Wednesday, October 18, 2023

Fixing PowerShell Scripting Error in C# Code: "Exception calling ""ToString"" with ""0"" argument(s). There is no Runspace available to run scripts in this thread."

A bit of a niche post here, but maybe someone is searching the interwebs and this will help save some time. When running a PowerShell script in C# leveraging the System.Management.Automation library, you might run into some neuanced issues that behave differently than when running PowerShell commands directly via the command line. Here is one instance, take the following PowerShell command to install a new Windows Service:
New-Service -Name "MyWindowsService" -BinaryPathName "C:\SomePath\1.0.1\MyWindowsService.exe"
When running this in a PowerShell terminal, it will generate the following output which as documented is an object representing the new service:
Status   Name               DisplayName
------   ----               -----------
Stopped  MyWindowsService   MyWindowsService
This is actually helpful output for a human, and maybe even for logging, bu when running this same command in C# via a PowerShell instance, the command will technically work, but you'll get the following cryptic error:
"The following exception occurred while retrieving the string: ""Exception calling ""ToString"" with ""0"" argument(s): ""There is no Runspace available to run scripts in this thread. You can provide one in the DefaultRunspace property of the System.Management.Automation.Runspaces.Runspace type. The script block you attempted to invoke was: $this.ServiceName""""", System.Object,System.ServiceProcess.ServiceController,System.WeakReference`1[System.Management.Automation.Runspaces.TypeTable], System.Management.Automation.PSObject+AdapterSet,None,System.Management.Automation.DotNetAdapter,"",System.Management.Automation.PSObject+AdapterSet, System.ServiceProcess.ServiceController,System.ServiceProcess.ServiceController,"PSStandardMembers {DefaultDisplayPropertySet}",False,False,False,False,False, "","",None
OK not very helpful. It took a while but I deduced the output from the New-Service cmdlet was causing this issue. There are 2 ways I found to handle this.
  1. Suppress the Output: Since this is running in C# without human intervention, pipe in the option to suppress the output if you are not logging it or using it for some meaningful purpose. Remember, you can still get the status by using Get-Service to inspect the newly installed service, as opposed to reading the output. Use the updated command to accomplish this:
    New-Service -Name "MyWindowsService" 
    -BinaryPathName "C:\SomePath\1.0.1\MyWindowsService.exe" | out-null
  2. Return the Output to a Variable: The other option is simply to return the output of the cmdlet typically for use to inspect. This could be done with the following updated statement:
    $newServiceResult = New-Service -Name "MyWindowsService" 
    -BinaryPathName "C:\SomePath\1.0.1\MyWindowsService.exe"
    Keep in mind he return value is of type System.ServiceProcess.ServiceController, so you can use that as needed.
For my needs I prefer option #1, as I don't need to inspect the return of New-Service and as mentioned can use Get-Service to further inspect any Windows Service status at this point. With either method though you will no longer get the cryptic error as previously shown.