I'm developing under .NET Framework 4.8. Note that I'm strictly locked to use .NET 4.8.
I've installed the "Microsoft.PowerShell.5.1.ReferenceAssemblies" Nuget package in order to be able automate Powershell commands, since the Nuget packages for PS6 and PS7 reuquires .net core at least.
The problem is that I'm unable to run the
Create a new project:
Windows Forms App (.NET Framework)
Download/install NuGet package:
Since you're using .NET Framework 4.8 and PowerShell v5.1, download/install NuGet package: Microsoft.PowerShell.5.1.ReferenceAssemblies
See Choosing the right PowerShell NuGet package for your .NET project for more information.
Add the following using directives:
Imports System.Management.Automation
Imports System.Management.Automation.Runspaces
Imports Microsoft.PowerShell
Then use one of the following:
Public Function PSGetModule() As String
Dim sb As System.Text.StringBuilder = New System.Text.StringBuilder()
'create the default initial session state.
Dim sessionState As InitialSessionState = InitialSessionState.CreateDefault()
sessionState.ExecutionPolicy = ExecutionPolicy.Unrestricted
sessionState.ImportPSModule({"C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Dism\Dism.psd1"})
'sessionState.ImportPSModule({"C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Dism"})
Using ps As PowerShell = PowerShell.Create(sessionState)
Dim results As ObjectModel.Collection(Of PSObject) = ps.AddCommand("Get-Module").Invoke()
'Dim results As ObjectModel.Collection(Of PSObject) = ps.AddScript("Get-Module").Invoke()
For Each result As PSObject In results
sb.AppendLine(result.ToString())
Next
End Using
Return sb.ToString()
End Function
Public Function PSGetModule2() As String
Debug.WriteLine($"PSGetModule2")
Dim sb As System.Text.StringBuilder = New System.Text.StringBuilder()
'create the default initial session state.
Dim sessionState As InitialSessionState = InitialSessionState.CreateDefault()
sessionState.ExecutionPolicy = ExecutionPolicy.Unrestricted
sessionState.ImportPSModule({"C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Dism\Dism.psd1"})
'sessionState.ImportPSModule({"C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Dism"})
Using rs As Runspace = RunspaceFactory.CreateRunspace(sessionState)
'open
rs.Open()
Using ps As PowerShell = PowerShell.Create()
ps.Runspace = rs
Dim results As ObjectModel.Collection(Of PSObject) = ps.AddCommand("Get-Module").Invoke()
For Each result As PSObject In results
sb.AppendLine(result.ToString())
Next
End Using
End Using
Return sb.ToString()
End Function
Public Async Function PSGetModuleAsync() As Task(Of String)
Dim sb As System.Text.StringBuilder = New System.Text.StringBuilder()
'create the default initial session state.
Dim sessionState As InitialSessionState = InitialSessionState.CreateDefault()
sessionState.ExecutionPolicy = ExecutionPolicy.Unrestricted
sessionState.ImportPSModule({"C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Dism\Dism.psd1"})
'sessionState.ImportPSModule({"C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Dism"})
Using ps As PowerShell = PowerShell.Create(sessionState)
ps.AddCommand("Get-Module")
Dim results = Await Task.Factory.FromAsync(ps.BeginInvoke(), Function(asyncResult As IAsyncResult) ps.EndInvoke(asyncResult))
For Each result As PSObject In results
sb.AppendLine(result.ToString())
Next
End Using
Return sb.ToString()
End Function
Usage:
Dim result As String = PSGetModule()
Debug.WriteLine($"result (PSGetModule): {result}")
Usage (Async)
Dim result As String = Await PSGetModuleAsync()
Debug.WriteLine($"result: {result}")
I was able to execute Get-WindowsDriver
, using code from this post. Here's the method that I used for testing (you may wish to change it to a function):
Public Sub GetSystemDrivers(flags As GetDriverFlags)
'create the default initial session state.
Dim sessionState As InitialSessionState = InitialSessionState.CreateDefault()
sessionState.ExecutionPolicy = ExecutionPolicy.Unrestricted
'sessionState.ImportPSModule("Dism")
'create a runspace. using the default host
Using rs As Runspace = RunspaceFactory.CreateRunspace(sessionState)
'open
rs.Open()
Dim hasFlagInbox As Boolean = flags.HasFlag(GetDriverFlags.Inbox)
Dim hasFlagNotInbox As Boolean = flags.HasFlag(GetDriverFlags.NotInbox)
'set value
Runspace.DefaultRunspace = rs
Using ps As PowerShell = PowerShell.Create()
ps.Runspace = rs
Dim dismDriverObjects = ps.AddCommand("Get-WindowsDriver").AddParameter("Online").Invoke()
For Each dismDriverObject As PSObject In dismDriverObjects
'create new instance
Dim driverInfo As New DismDriverInfo(dismDriverObject)
If flags <> GetDriverFlags.Any Then
If (hasFlagInbox AndAlso Not driverInfo.Inbox) OrElse
(hasFlagNotInbox AndAlso driverInfo.Inbox) Then
Continue For
End If
End If
Debug.WriteLine($"Driver: {driverInfo.DriverFile}")
Debug.WriteLine($"Date: {driverInfo.BuildDate}")
Debug.WriteLine($"Version: {driverInfo.Version}")
Next
End Using
End Using
End Sub
Note: Since Get-WindowsDriver
requires administrative privileges, add an Application Manifest File
(Project => Add New Item... => Application Manifest File) to your project. Then change from <requestedExecutionLevel level="asInvoker" uiAccess="false" />
to <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
.
Resources:
- Choosing the right PowerShell NuGet package for your .NET project
- How to automate either PowerShell or PowerShell Core for same machine
- PowerShell
- Install PowerShell on Windows, Linux, and macOS
- Creating an InitialSessionState
- PowerShell - Adding and invoking commands
- TaskFactory.FromAsync Method
- Lambda Expressions (Visual Basic)
- Use DISM in Windows PowerShell
- Use DISM in Windows PowerShell (<= Win 8.1)
- Beginning Use of PowerShell Runspaces: Part 1
CodePudding user response:
I found that the problem is related to missing dependencies for some processor architectures, related to DISM. I'm on Windows 10 x64 so my PowerShell automation code based on DISM / Get-WindowsDriver cmdlet will only run successful (without exceptions) when compiling my application to x64.
The problem is that I was compiling to "AnyCPU", and that is why PowerShell was not able to find DISM module and it was throwing that exception. Don't ask me if this architecture issue is normal, but that was the root of the problem, and it throws the same exception when targeting x86 architecture. So the solution is to compile for x64.
I just will share for the record this generic usage function that I used to run Get-Module cmdlet to ensure that DISM module is loaded:
<DebuggerStepThrough>
Public Shared Function ExecutePowerShellScript(script As String, ParamArray importModules As String()) As Collection(Of PSObject)
Dim sessionState As InitialSessionState = InitialSessionState.CreateDefault()
sessionState.ExecutionPolicy = ExecutionPolicy.Unrestricted
sessionState.ImportPSModule(importModules)
Dim results As Collection(Of PSObject)
Dim errors As PSDataCollection(Of ErrorRecord)
Using runspace As Runspace = RunspaceFactory.CreateRunspace(sessionState)
' Save current default runspace, if any.
Dim previousDefaultRunSpace As Runspace = Runspace.DefaultRunspace
' By using this custom default runspace on which we have specified "ExecutionPolicy.Unrestricted" policy,
' we ensure that PowerShell don't run on new/different threads that can have restricted execution policies,
' otherwise it will throw an exception due the restriction policy.
Runspace.DefaultRunspace = runspace
Runspace.DefaultRunspace.Open()
Using ps As PowerShell = PowerShell.Create(RunspaceMode.CurrentRunspace)
ps.AddScript(script)
Try
results = ps.Invoke()
' The PowerShell environment has a "PowerShell.HadErrors" property
' that indicates in the assumption whether errors have occurred.
' Unfortunately, most often, I have found that despite errors
' its value can be "False", and vice versa, I have found that
' in the absence of errors its value can be "True".
' Therefore, we check the fact that errors have occurred,
' using the error counter in "PowerShell.Streams" property:
If ps.Streams.Error.Any() Then
errors = New PSDataCollection(Of ErrorRecord)()
Throw errors.First().Exception
End If
Catch ex As Exception
Throw
End Try
Runspace.DefaultRunspace.Close()
' Restore previous default runspace, if any.
Runspace.DefaultRunspace = previousDefaultRunSpace
End Using
End Using
Return results
End Function
Example Usage:
Dim importModules As String() = {"Dism", "PSReadline", "WindowsSearch"}
Dim script As String = "Get-Module | Format-Table -Property Name, Version, Moduletype | Out-String"
Dim results As Collection(Of PSObject) = ExecutePowerShellScript(script, importModules)
Debug.WriteLine(results.Single().ToString())