One of the issues of running FIM 2010 R2 on Windows Server 2012 is calling PowerShell scripts from within FIM Portal Workflows (.NET). It seems the workflow code is running .NET 3.5 but uses PowerShell 2.0. When we started migrating our FIM 2010 to MIM 2016 (on Server 2012 R2) we ran into the same issues. This is the .NET code that has been running fine on Windows 2008 R2 for years without any issues:
RunspaceConfiguration config = RunspaceConfiguration.Create();
Runspace runspace = RunspaceFactory.CreateRunspace(config);
psh = PowerShell.Create();
psh.Runspace = runspace;
And one the scripts that was executed contained code like this:
Now when porting that same logic to our MIM 2016 running on Windows Server 2016 we saw that our get-AD* cmdlets returned nothing. After some investigation we found the following error was triggered when running import-module Active Directory: The 'C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\ActiveDirectory\ActiveDirectory.psd1' module cannot be imported because its manifest contains one or more members that are not valid. The valid manifest members are ('ModuleToProcess', 'NestedModules', 'GUID', 'Author', 'CompanyName', 'Copyright', 'ModuleVersion', 'Description', 'PowerShellVersion', 'PowerShellHostName', 'PowerShellHostVersion', 'CLRVersion', 'DotNetFrameworkVersion', 'ProcessorArchitecture', 'RequiredModules', 'TypesToProcess', 'FormatsToProcess', 'ScriptsToProcess', 'PrivateData', 'RequiredAssemblies', 'ModuleList', 'FileList', 'FunctionsToExport', 'VariablesToExport', 'AliasesToExport', 'CmdletsToExport'). Remove the members that are not valid ('HelpInfoUri'), then try to import the module again.
There are various topics online that cover this exact issue.
It seems some PowerShell modules are hardwired to require PowerShell v3. I came across the following suggestion a few times, but it scares me a bit as with my (limited?) knowledge of .NET It’s hard to estimate what impact this might have on FIM. The suggestion was to add the following to the Microsoft.ResourceManagement.Service.exe.config file.
I found some approaches using a script that calls another script, but I wanted to avoid this. So I came up with the following approach to update the workflow itself:
PowerShellProcessInstance instance = new PowerShellProcessInstance(new Version(3, 0), null, null, false);
Runspace runspace = RunspaceFactory.CreateOutOfProcessRunspace(new TypeTable(new string), instance);
The PowerShellProcessInstance is class that is available in System.Management.Automation. And that’s part of PowerShell itself. I tried various DLLS, but either they didn’t know the class or they resulted in the following error when building my .NET project:
The primary reference "System.Management.Automation" could not be resolved because it has an indirect dependency on the .NET Framework assembly "System, Version=188.8.131.52, Culture=neutral, PublicKeyToken=b77a5c561934e089" which has a higher version "184.108.40.206" than the version "220.127.116.11" in the current target framework. FODJ.FIM.Workflow.ActivityLibrary
My project is configured to build for .NET 3.5, but If I’m not mistaken .NET 3.5 use CLR 2.0. Whilst .net 4/4.5 use CLR 4.0 (see .NET Framework Versions and Dependencies). So I guess this route isn’t going to work after all. Back to the drawing board. As I only got a number of scripts to call like this, I decided to go back the wrapper script approach:
The script containing the logic to be executed:
As you can see I prepended .script to the .ps1 extension. And here’s my wrapper script. This is the one that is called from the FIM/MIM Workflow:
There are some things to note: the param line is just a copy past from the base script. And I just specify them as parameters again when calling the base script. I had been looking for a way to use unbound parameters, e.g. the calling workflow says –username … –department … and the wrapper script just passes it over. That would have allowed me to have a generic wrapper script. I got pretty close to getting it to work, but I kept running into issues. In the end I just decided to go for KISS.
Note: if you want to capture errors like the one I show from “import-module Active Directory”, just use the $error variable. You can use it like this. Saving it to disk is just one example. Typically you could integrate this with your logging function.