Search code examples
.netpowershellmsbuildcsproj

How can I call code that uses `Start-ThreadJob` from the PowerShell SDK?


I have an app that is using the PowerShell SDK to execute PowerShell code in the background.

However, that code depends on Start-Job, and the PowerShell script is throwing an exception when I run it in my app:

_powerShellInstance.AddScript("DoSomethingWithStartJob").Invoke();

The pwsh executable cannot be found at "C:\Users\ben\Projects\Foo\Foo (Package)\bin\x64\Debug\AppX\Foo\runtimes\win\lib\net8.0\pwsh.exe". Note that 'Start-Job' is not supported by design in scenarios where PowerShell is being hosted in other applications. Instead, usage of the 'ThreadJob' module is recommended in such scenarios.

I want to port to use Start-ThreadJob like the error says, but I don't know how! How can I get my C# app to install the ThreadJob module into my PowerShell SDK installation?


Solution

  • This should work for any PS module available in PSGallery. To support ThreadJob specifically:

    1. Added ThreadJob's PSGallery nuget package to our feed
    2. Created a custom build step to manually copy the ThreadJob module contents to our output dir.

    Adding ThreadJob to the feed

    In your nuget.config (example nuget.config):

      <packageSources>
        <clear />
        <!-- ... -->
        <add key="PSGallery" value="https://www.powershellgallery.com/api/v2/" />
      </packageSources>
    

    In your csproj (example csproj; in our case, we were deploying a WinUI project so we edited the package wapproj instead):

    <ItemGroup>
      <!-- ... -->
      <PackageReference Include="ThreadJob" Version="2.0.3">
        <!-- Necessary for the next step: -->
        <GeneratePathProperty>True</GeneratePathProperty>
      </PackageReference>
    </ItemGroup>
    

    This adds the pacakge to your project.

    In our case, we had a custom NuGet feed in ADO, so we just added ThreadJob to its cache.

    Create custom build step

    In the same csproj/wapproj that references the ThreadJob package:

      <Target Name="CopyThreadJob" AfterTargets="AfterBuild">
        <Message Text="ThreadJob module files: @(ThreadJobFiles)" Importance="high"></Message>
        <Message Text="Deploying ThreadJob module to: $(OutDir)AppX\Foo\runtimes\win\lib\net8.0\Modules\Microsoft.PowerShell.ThreadJob\" Importance="high"></Message>
          <!-- If you want other targets, like UNIX, you'll need to copy to those Modules\ directories, too -->
          <!-- Remember, the subdir in Modules needs to exactly match the name of the .psd1 -->
          <Copy SourceFiles="@(ThreadJobFiles)" DestinationFolder="$(OutDir)\AppX\Foo\runtimes\win\lib\net8.0\Modules\Microsoft.PowerShell.ThreadJob\" SkipUnchangedFiles="true" />
      </Target>
    

    Now your code can invoke Start-ThredJob!

    _powerShellInstance.AddScript("Start-ThreadJob").Invoke();