Remote Powershell Script XMA

I promised to write up my Remote Powershell XMA. This one will run a powershell script on another server. The powershell script generates a text file of data which you then use to import your connector space. If you need to add any PSSnapins you do that as part of the script.

I’m only showing the Import step here, but it should be pretty straight forward to extrapolate to running an export script against a text file of changed data, if you need to do that. I also have another post coming soon which will show how to run individual powershell cmdlets for single object changes, instead of the bulk approach used here.

Note that I already wrote about how to create a powershell XMA that runs local powershell scripts in this post.

1. Prepare system prerequisites

There are some prereqs for remote powershell. See this post for details.

2. Write your powershell script

Start by writing your powershell script, which should gather whatever objects and attributes you want to manage (in the screenshots below I’m using Exchange mailboxes as an example) and then dumps the info into one of the supported text file formats. CSV is most straight forward due to the export-csv cmdlet, but if you have any multi-value attributes you’ll need to look into AVP.

You will need a copy of the text file produced by the script before you create the MA, as it will form the basis of the MA’s schema.

3. Create the Extensible MA

Create an MA of type “Extensible Connectivity”.

I always leave the Architecture on “Process” and it seems to work – to tell the truth I haven’t found out yet what the other options are for.

It is also a best practise to tick “run in a seperate process” for XMAs, though you will have to temporarily remove this tick if you need to debug your code by attaching the Visual Studio debugger to the miiserver.exe process.

I’m creating a “File-based” XMA here, but a “Call-based” will work just as well as both types use a text file to stage the import data.

I must also specify a name for the CSExtension dll here. It doesn’t matter if you haven’t actually written the dll yet. You can choose any existing dll and come back and change it later, or just enter the name you plan to use for the dll.

Finally I am using the Connection information to specify the name of the remote server where the powershell script is, and the account I should use to connect and run the script.

Also, very usefully, you can use the Additional Parameters page if you need to specify some command line options to pass to the remote powershell script. Here I’m specifying the name of the file I want the script to produce, and also some extra credentials that the script needs to work. Notice I can even hide the extra password from prying eyes – neat!
On this page I feed the wizard a copy of the file produced by my powershell script. It doesn’t have to be a complete copy of all the data you will be importing – the point here is to allow the MA to configure its schema.
My file in this case is a CSV so I specify the delimiter and that there is a header row – all pretty obvious.
I choose which attribute to treat as the anchor, or identifier. If you don’t have a single unique attribute you can use multiple attributes to form a unique anchor.
I’ve skipped the next few pages because they’re the same for all MAs, so once I’m done I can click Finish and the MA is created.

4. Create the CSExtension Project

With the new MA selected, click Create Extension project. Select type “Connected Data Source Extension” from the dropdown and give the project the same name you entered when creating the MA (or go back and change the  dll name in the MA configuration to match this one).

I always untick the opion to open the project immediately because I will be adding the new project into my Visual Studio Solution and opening it from there.

If you like, at this point, you can compile the empty project (just so the dll exists), copy your text file into the newly created MaData\MAName folder, and run an Import straight away.

5. The Extension Code

And here is what the extension code looks like. It’s fairly generic, and obviously you need to do the powershell part, but hopefully this will be enough to get you started! 

Imports Microsoft.MetadirectoryServices
Imports System.Management.Automation
Imports System.Management.Automation.Host
Imports System.Management.Automation.Runspaces
Imports System.IO

Public Class MAFileExport
    Implements IMAExtensibleFileImport
    Implements IMAExtensibleFileExport

    Const DATA_SHARE As String = "data"
    Const MA_FOLDER As String = "C:\Program Files\Microsoft Forefront Identity Manager\2010\Synchronization Service\MaData\RemotePowershell\"
    Const IMPORT_FILE As String = "import.csv"

    Public Sub GenerateImportFile(ByVal filename As String, ByVal connectTo As String, ByVal user As String, ByVal password As String, ByVal configParameters As ConfigParameterCollection, ByVal fullImport As Boolean, ByVal types As TypeDescriptionCollection, ByRef customData As String) Implements IMAExtensibleFileImport.GenerateImportFile
        Dim fileOut As StreamWriter = New StreamWriter(MA_FOLDER & IMPORT_FILE, False, System.Text.Encoding.Default)

        '' Open remote powershell and run the powershell script that gets the import data.
        '' Note that name of the file the script is to produce is passed as a script argument via a configParameter,
        '' which has been configured on the Additional Parameters page of the MA.

        RunRemotePSScript(connectTo, user, password, _
                             "c:\scripts\Get-mailboxes.ps1 " & configParameters("exportFile").Value)

        '' Copy the data file to the FIM server. The folder on the remote server has been shared to make this easier.

        Dim fileData As String = "\\" & connectTo & "\" & DATA_SHARE & "\" & configParameters("exportFile").Value
        If File.Exists(fileData) Then
            File.Copy(fileData, MA_FOLDER & "\" & IMPORT_FILE)
        Else
            Throw New UnexpectedDataException("File not found " & fileData)
        End If

    End Sub

    Public Sub DeliverExportFile(ByVal filename As String, ByVal connectTo As String, ByVal user As String, ByVal password As String, ByVal configParameters As ConfigParameterCollection, ByVal types As TypeDescriptionCollection) Implements IMAExtensibleFileExport.DeliverExportFile
        ' TODO: Remove this throw statement if you implement this method
        Throw New EntryPointNotImplementedException
    End Sub

    Sub RunRemotePSScript(ByVal connectTo As String, ByVal user As String, ByVal password As String, ByVal script As String)
        Const SHELL_URI As String = "http://schemas.microsoft.com/powershell/Microsoft.PowerShell"
        Dim serverUri As New Uri("http://" & connectTo & ":5985/wsman")

        Dim securepass As New Security.SecureString
        Dim c As Char
        For Each c In password
            securepass.AppendChar(c)
        Next
        Dim remotecred As New PSCredential(user, securepass)

        Dim connectionInfo As New WSManConnectionInfo(serverUri, SHELL_URI, remotecred)
        Dim myRunSpace As Runspace = RunspaceFactory.CreateRunspace(connectionInfo)
        Dim psresult As New System.Collections.ObjectModel.Collection(Of PSObject)
        myRunSpace.Open()

        Dim psh As PowerShell = PowerShell.Create()
        psh.Runspace = myRunSpace

        psh.AddScript(script)
        psresult = psh.Invoke()
        psh.Dispose()
        myRunSpace.Close()
    End Sub

End Class

Postscript

If the types Powershell and WSManConnectionInfo are not recognised you may have an old version of the System.Management.Automation.dll. Make sure you install the WinRM update before copying the dll out of the GAC. The file version I am working with is 9/10/2009 (or 10/9/2009 in american). If you have the old version then get the new one, copy it into your project folder, and then remove and re-add the reference.