1

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 Get-WindowsDriver cmdlet in this scenario. I get this exception message when invoking the Get-WindowsDriver command:

The term 'Get-WindowsDriver' is not recognized as the name of a cmdlet, function, script file, or operable program

I've tried to import the DISM module via a call to InitialSessionState.ImportPSModule (using the full file path to the Dism.psd1 file) and also I tried to import he module invoking the Import-Module cmdlet. In both cases and from what I observed it seems to do absolutely nothing of nothing, because if I invoke the Get-Module cmdlet (to try verify that the DISM module was loaded correctly) it does not return any single result nor produces any error.

This is the code I'm trying:

    Dim state As InitialSessionState = InitialSessionState.CreateDefault()
    state.ExecutionPolicy = ExecutionPolicy.Unrestricted
    state.ImportPSModule({"C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Dism\Dism.psd1"})
    Dim runspace As Runspace = RunspaceFactory.CreateRunspace(state)
    Runspace.DefaultRunspace = runspace
    Runspace.DefaultRunspace.Open()

    Dim ps As PowerShell = PowerShell.Create(RunspaceMode.CurrentRunspace)
    ps.Runspace = runspace
    ps.Commands.AddScript("Get-Module | Out-String")

    For Each result As PSObject In ps.Invoke
        Debug.WriteLine(result.ToString())
    Next result

Can I call Get-WindowsDriver cmdlet under .net 4.8 with PS 5.1?, and how to do that?.

Please note that I don't have this problem if I use the same code under net 5.0/6.0 (using "Microsoft.Powershell.*" v7.1.7 nuget packages like in this code example that I compiled and executed with no problems).

ElektroStudios
  • 19,105
  • 33
  • 200
  • 417

3 Answers3

2

One can use PowerShell 7 in a Windows Forms App (.NET Framework) project by using System.Diagnostics.Process. Below shows how to use Process to run PowerShell and retrieve the output.

Add the following Imports:

  • Imports Microsoft.Win32
  • Imports System.IO

Add the following code:

Public Sub ExecutePowerShell(arguments As String, Optional encoding As System.Text.Encoding = Nothing)
    Dim sb As System.Text.StringBuilder = New System.Text.StringBuilder()

    'get fully-qualified PowerShell filename
    Dim powerShellFilename As String = GetPowerShellFullyQualifiedFilename()

    Debug.WriteLine($"powerShellFilename: {powerShellFilename}")
    Debug.WriteLine($"arguments: {arguments}")

    'create new instance
    Dim startInfo As ProcessStartInfo = New ProcessStartInfo(powerShellFilename)

    'set values
    startInfo.Arguments = arguments
    startInfo.CreateNoWindow = True 'don't create a window

    'if specified, set encoding
    If encoding IsNot Nothing Then
        startInfo.StandardErrorEncoding = encoding
        startInfo.StandardOutputEncoding = encoding
    End If

    startInfo.RedirectStandardError = True 'redirect StandardError
    startInfo.RedirectStandardInput = False 'don't redirect StandardInput
    startInfo.RedirectStandardOutput = True ' redirect StandardInput
    startInfo.UseShellExecute = False 'If True, uses 'ShellExecute'; if false, uses 'CreateProcess'
    startInfo.Verb = "runas" 'run elevated
    startInfo.WindowStyle = ProcessWindowStyle.Hidden 'hide window

    'create new instance and set properties
    Using p As Process = New Process() With {.EnableRaisingEvents = True, .StartInfo = startInfo}
        'subscribe to events (add event handlers)
        AddHandler p.ErrorDataReceived, AddressOf P_ErrorDataReceived
        AddHandler p.OutputDataReceived, AddressOf P_OutputDataReceived

        'start
        p.Start()

        'begin async reading for both standard error and standard output
        p.BeginErrorReadLine()
        p.BeginOutputReadLine()

        'wait until the process is finished before continuing
        p.WaitForExit()

        'unsubscribe from events (remove event handlers)
        RemoveHandler p.ErrorDataReceived, AddressOf P_ErrorDataReceived
        RemoveHandler p.OutputDataReceived, AddressOf P_OutputDataReceived
    End Using
End Sub

Private Sub P_ErrorDataReceived(sender As Object, e As DataReceivedEventArgs)
    If Not String.IsNullOrEmpty(e.Data) Then
        'ToDo: add desired code
        Debug.WriteLine("error: " & e.Data)
    End If
End Sub

Private Sub P_OutputDataReceived(sender As Object, e As DataReceivedEventArgs)
    If Not String.IsNullOrEmpty(e.Data) Then
        'ToDo: add desired code
        Debug.WriteLine("output: " & e.Data)
    End If
End Sub

Public Function GetPowerShellFullyQualifiedFilename(Optional regView As RegistryView = RegistryView.Registry64) As String
    Dim installLocation As String = String.Empty
    Dim powerShellFilename As String = String.Empty
    Dim version As String = String.Empty

    'get PowerShell version < 7
    Using localKey As RegistryKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, regView)
        If localKey IsNot Nothing Then
            Using subkey As RegistryKey = localKey.OpenSubKey("SOFTWARE\Microsoft\PowerShell", False)
                If subkey IsNot Nothing Then
                    For Each key As String In subkey.GetSubKeyNames() 'ex: 3
                        Using subkey2 As RegistryKey = subkey.OpenSubKey(Path.Combine(key, "PowerShellEngine")) 'ex: 3\PowerShellEngine
                            installLocation = subkey2.GetValue("ApplicationBase", String.Empty).ToString()

                            If File.Exists(Path.Combine(installLocation, "powershell.exe")) Then
                                powerShellFilename = Path.Combine(installLocation, "powershell.exe")
                            End If

                            version = subkey2.GetValue("PowerShellVersion", String.Empty).ToString()
                            Debug.WriteLine($"installLocation: '{installLocation}' version: '{version}'")
                        End Using
                    Next
                End If
            End Using
        End If
    End Using

    'check if PowerShell v7 is installed
    Using localKey As RegistryKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, regView)
        If localKey IsNot Nothing Then
            Using subkey As RegistryKey = localKey.OpenSubKey("SOFTWARE\Microsoft\PowerShellCore\InstalledVersions", False)
                If subkey IsNot Nothing Then
                    For Each key As String In subkey.GetSubKeyNames()
                        Using subkey2 As RegistryKey = subkey.OpenSubKey(key)
                            installLocation = subkey2.GetValue("InstallLocation", String.Empty).ToString()

                            If File.Exists(Path.Combine(installLocation, "powershell.exe")) Then
                                powerShellFilename = Path.Combine(installLocation, "powershell.exe")
                            ElseIf File.Exists(Path.Combine(installLocation, "pwsh.exe")) Then
                                powerShellFilename = Path.Combine(installLocation, "pwsh.exe")
                            End If

                            version = subkey2.GetValue("SemanticVersion", String.Empty).ToString()
                            Debug.WriteLine($"installLocation: '{installLocation}' version: '{version}'")
                        End Using
                    Next
                End If
            End Using
        End If
    End Using

    Return powerShellFilename
End Function

Note: Modify the code within P_ErrorDataReceived and P_OutputDataReceived as desired.

Usage (Get-Module):

ExecutePowerShell("-NoLogo -Command ""& {Get-Module | Out-String}""")

Note: When a double-quote (") is used within double-quotes, it's necessary to escape it. To escape it, one adds a second double-quote. For information on why the &, is used, see pwsh -Command


Certain PowerShell commands may require elevation (administrative privileges). If one desires to execute one of these commands, such as Get-WindowsDriver, add an Application Manifest File to your project.

Add Application Manifest File:

  • In VS menu, click Project
  • Select Add New Item...
  • Select Application Manifest File (name: app.manifest)
  • Click Add

Open Solution Explorer:

  • In VS menu, click View
  • Select Solution Explorer

Modify requestedExecutionLevel:

  • In Solution Explorer, right-click app.manifest and select Open

Change From:

<requestedExecutionLevel  level="asInvoker" uiAccess="false" />

Change To:

<requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />

Usage (Get-WindowsDriver):

ExecutePowerShell("-NoLogo -Command ""& {Get-WindowsDriver -Online | Out-String}""")

Resources:

Tu deschizi eu inchid
  • 4,117
  • 3
  • 13
  • 24
1

Try the following, I've tested it on Win 10.

Get PowerShell version:

  • Open PowerShell and run:

    Get-Host | Select-Object Version

enter image description here

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:

Tu deschizi eu inchid
  • 4,117
  • 3
  • 13
  • 24
  • Thanks for answer, but on my side PSGetModule and PSGetModule2 returns nothing (empty results), and if I call GetSystemDrivers if throws the error that I mentioned ("The term 'Get-WindowsDriver' is not recognized as the name of a cmdlet, function, script file, or operable program"). I'm on Windows 10.0.19041.450 x64, with Powershell 5.1.19041.1. I also did the changes you suggested in the manifest file, although I'm running on the built-in Admin account. – ElektroStudios Oct 09 '22 at 07:33
  • I'm going to mark this as the accepted solution due the user effort for writing and testing and giving many resource urls, but if someone is facing the same problem as me note that the real solution (at least on my side) is the one that I poster here: https://stackoverflow.com/a/74003295/1248295 – ElektroStudios Oct 09 '22 at 08:44
0

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 ExecutePowerShellAction(powerShellAction As Action(Of PowerShell), ParamArray importModules As String()) As Collection(Of PSObject)

    Dim results As Collection(Of PSObject)

    Dim sessionState As InitialSessionState = InitialSessionState.CreateDefault()
    sessionState.ExecutionPolicy = ExecutionPolicy.Unrestricted
    sessionState.ImportPSModule(importModules)

    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)

            Try
                powerShellAction.Invoke(ps)
                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
                    Throw ps.Streams.Error.First().Exception
                End If

            Catch ex As Exception
                Throw ' ps.Streams.Error.First().Exception

            End Try

        End Using

        ' Restore previous default runspace, if any.
        ' ( Note that due the "Using" statement we can ignore to call "Runspace.DefaultRunspace.Close()" )
        Runspace.DefaultRunspace = previousDefaultRunSpace

    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|Out-String"
Dim powerShellAction As New Action(Of PowerShell)(Sub(ps) ps.AddScript(script))

Dim results As Collection(Of PSObject) = 
    ExecutePowerShellAction(powerShellAction, importModules)

Debug.WriteLine(results.Single().ToString())
ElektroStudios
  • 19,105
  • 33
  • 200
  • 417
  • In my testing, I compiled as `Any CPU`, `Prefer 32-bit`. It ran successfully and returned data. If possible, you may try updating PowerShell 5.1 to the latest version - it looks like you and I are using different versions. Or you can just continue to use the workaround that you discovered. – Tu deschizi eu inchid Oct 09 '22 at 15:29
  • 1
    You may try downloading/installing the [Windows ADK for Windows 10, version 2004](https://learn.microsoft.com/en-us/windows-hardware/get-started/adk-install#other-adk-downloads) as described [here](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-8.1-and-8/hh825010(v%3Dwin.10)#to-prepare-the-dism-powershell-environment). It will be installed to `%ProgramFilex(x86)%\WIndows Kits\10`. If executing as 32-bit, you may try using the x86 version of DISM: `%ProgramFilex(x86)%\WIndows Kits\10\Assessment and Deployment Kit\Deployment Tools\x86\DISM`. – Tu deschizi eu inchid Oct 09 '22 at 15:34