fredag den 4. marts 2011

Implementing PowerShell in applications - Part 1

I created a few applications doing the last 2 years that facilitate PowerShell. I really had a hard time getting around to look at PowerShell, I honestly though VB scripts where so more powerful and easier to write, but looking back now I really don’t see why. I love it. I love it so much, that I create several applications that have a built-in PowerShell engine.
And how does that work then, you might ask ? well, there's tons of examples out there, but do a Google search on “Runspaces” or “Management.Automation.Runspaces” and your well on your way. There is how ever a few things, you want to keep in mind.
A plain PowerShell will often not be enough so you need some modules or snap ins. Let get down to basics here. Create a new application, go to My Project –> References –> Add –> Browse –> C:\Windows\assembly\GAC_MSIL\System.Management.Automation and find System.Management.Automation.dll

Imports System.Management.Automation
Imports System.Management.Automation.Runspaces

Module Module1

    Sub Main()
        Dim output As ICollection(Of PSObject) = Nothing
        Dim ps As PowerShell = PowerShell.Create()
        ps.AddScript("get-help")
        ps.Commands.Commands.Item(ps.Commands.Commands.Count - 1).MergeUnclaimedPreviousCommandResults = _
        PipelineResultTypes.Error + PipelineResultTypes.Output
        Try
            output = ps.Invoke()
        Catch ex As Exception
            Throw ex
        End Try
        For Each o As PSObject In output
            If Not o Is Nothing Then
                Console.WriteLine(o.ToString)
            End If
        Next
        Console.ReadLine()
    End Sub
End Module

See, that wasn’t so bad was it ? Here's with some Snap ins loaded from start. ( you can also just call invoke “Add-PSSnapin Name” .. use Get-PSSnapin –Registered to see what is available to you.

Here's another “gotcha” … Many snap ins only works on x86 and some only on x64. If you see something in your PowerShell and get The Windows PowerShell snap-in 'SqlServerCmdletSnapin100' is not installed on this machine.

image


It means you compiling for the wrong version. Go to My Project –> Advanced Compiler Options…” and choose the correct version. NEVER choose anyCPU.

Sub Main()
    Dim rc As RunspaceConfiguration = RunspaceConfiguration.Create
    Dim SnapInList As String() = {"SqlServerCmdletSnapin100", "SqlServerProviderSnapin100"}
    For Each snapin As String In SnapInList
        Dim snapEx As PSSnapInException = Nothing
        Dim info As PSSnapInInfo = rc.AddPSSnapIn(snapin, snapEx)
        If (Not snapEx Is Nothing) Then Throw New Exception(snapEx.Message, snapEx)
    Next
    Dim r As Runspace = RunspaceFactory.CreateRunspace(rc)
    r.Open()
    Dim output As ICollection(Of PSObject) = Nothing
    Dim ps As PowerShell = PowerShell.Create()
    ps.Runspace = rps.AddScript("Get-ChildItem SQLSERVER:\SQL\SQL01\DEFAULT\Databases")
    ps.Commands.Commands.Item(ps.Commands.Commands.Count - 1).MergeUnclaimedPreviousCommandResults = _
    PipelineResultTypes.Error + PipelineResultTypes.Output
    Try
        output = ps.Invoke()
    Catch ex As Exception
        Throw ex
    End Try
    For Each o As PSObject In output
        If Not o Is Nothing Then
            Console.WriteLine(o.ToString)
        End If
    Next
    Console.WriteLine("done.")
    Console.ReadLine()
End Sub



Ok, all seems ok, now try this.

Sub Main()
    Dim output As ICollection(Of PSObject) = Nothing
    Dim ps As PowerShell = PowerShell.Create()
    ps.AddScript("Write-Host 'Hello world.'")
    ps.Commands.Commands.Item(ps.Commands.Commands.Count - 1).MergeUnclaimedPreviousCommandResults = _
        PipelineResultTypes.Error + PipelineResultTypes.Output
    Try
        output = ps.Invoke()
    Catch ex As Exception
        Throw ex
    End Try
    For Each o As PSObject In output
        If Not o Is Nothing Then
            Console.WriteLine(o.ToString)
        End If
    Next
    Console.WriteLine("done.")
    Console.ReadLine()
End Sub

It’s blank … the reason is PowerShell commands return objects, not text. Write-Host is sending the text to the Host holding the runespace. the handy little wrapper class PowerShell implements a simple Host and HostUI, but this doesn’t support neat things like Read, Write, Prompt etc..

So, lets make one, that does. ( Remember to add a reference to “System.Drawing” )
Add the following 3 classes

myPSHost.vb
myPSHostRawUI.vb
myPSHostUI.vb

Now, you can auto accept warnings that is making your life miserably and avoid getting weird errors when enabling Exchange 2010 PowerShell add-on and initializing it.

Sub Main()
    Dim host As New myPSHost
    Dim rc As RunspaceConfiguration = RunspaceConfiguration.Create
    Dim r As Runspace = RunspaceFactory.CreateRunspace(host, rc)
    r.Open()
    Dim output As ICollection(Of PSObject) = Nothing
    Dim ps As PowerShell = PowerShell.Create()
    ps.Runspace = r
    ps.AddScript("Write-Host 'Hello world.'")
    ps.Commands.Commands.Item(ps.Commands.Commands.Count - 1).MergeUnclaimedPreviousCommandResults = _
        PipelineResultTypes.Error + PipelineResultTypes.Output
    Try
        output = ps.Invoke()
    Catch ex As Exception
        Throw ex
    End Try
    For Each o As PSObject In output
        If Not o Is Nothing Then
            Console.WriteLine(o.ToString)
        End If
    Next
    For Each e As myPSHost.UIAction In host.UIEvents
        If e.ActionType = "Write" Then
            Console.WriteLine(e.Message)
        End If
    Next
    Console.WriteLine("done.")
    Console.ReadLine()
End Sub

Ingen kommentarer:

Send en kommentar