Search code examples
excelvbapowershellelevated-privileges

Running Powershell from VBA with Administrator privileges


I have not been able to successfully run my PowerShell script from VBA with administrator privileges.

I have examined the various answers in this forum and attempted to adapt with no success.

When I execute the VBA script below, there is a window that will open for less than one second and then close (and I don't know if it is a Powershell or a Command window) and the script does not run as evidenced by not doing its job, and finishing immediately (not waiting the ten seconds). Also, there is nothing written to StdOut or StdErr

If the script is run from an elevated command window, or an elevated Powershell window, it runs as designed.

The script is designed to temporarily disconnect the network adapter. It is simplified at this stage and only works on an adapter named Ethernet.

Powershell Script

Disable-NetAdapter -name Ethernet -confirm:$false
Start-Sleep -Seconds 10
Enable-NetAdapter -name Ethernet -confirm:$false

Functioning elevated command window code:
C:\PowerShell Scripts>"disable network temporarily.ps1" enter image description here

Non-Functioning VBA Code

'Needs reference to Windows Script Host Object Model
Option Explicit
Sub TempNetworkDisable()
    Dim wshShell As wshShell
    Dim wshShellExec    As Object
    Dim strCommand      As String
    Dim strOutput

    strCommand = "pwsh.exe -c Start-Process -Verb RunAs pwsh.exe \"" -ExecutionPolicy Unrestricted -NoExit -f `\""C:\PowerShell Scripts\Disable Network Temporarily.ps1`\"""
    
    Set wshShell = New wshShell
    Set wshShellExec = wshShell.Exec(strCommand)
    strOutput = wshShellExec.StdOut.ReadAll()
    Debug.Print "StdOut:", strOutput

    strOutput = wshShellExec.StdErr.ReadAll()
    Debug.Print "StdErr:", strOutput
End Sub
  • Windows 11 Pro
    • Version 10.0.22631 Build 22631
  • PSVersion 7.4.6
  • Microsoft® Excel® for Microsoft 365 MSO (Version 2409 Build 16.0.18025.20160) 64-bit
  • VBA 7.1.1143

Solution

    • The immediate problem with your approach is that you were missing an escaped closing ":[1]

      ' Note the \"" before the closing "
      ' To run the elevated pwsh.exe process *synchronously*, i.e. to wait
      ' for it to exit, add -Wait before -Verb RunAs
      strCommand = "pwsh.exe -c Start-Process -Verb RunAs pwsh.exe \""-ExecutionPolicy Unrestricted -NoExit -f `\""C:\PowerShell Scripts\Disable Network Temporarily.ps1`\""\"""
      
      • Note that if you run the resulting command line from a non-elevated process, you'll get an interactive UAC prompt to authorize creation of an elevated process (a process running with administrative privileges, as requested via Start-Process -Verb RunAs).

      • The only way to avoid UAC prompts is to either run in an elevated process to begin with or to disable (parts of) UAC altogether, which is strongly discouraged. Otherwise, there's only a limited workaround: Assuming that you are an administrator in principle, you can set up a scheduled task with a preconfigured command to be run with elevation, which you can then invoke on demand from a non-elevated process of yours without triggering a UAC prompt.
        See this answer for more information.

    • However, this presents a follow-up problem: you're reading the stdout and stderr streams of the outer, transitory pwsh.exe, which will be empty, given that the nested, elevated pwsh.exe process launches in a new window, which means that its output streams aren't connected to the outer process.

      • Note:

        • Neither your outer pwsh.exe call (the one that facilitates launching another pwsh.exe process with elevation) nor your *.ps1 script are designed to produce output, so perhaps you don't need to solve this problem - assuming that no errors occur; if you do want to capture any errors, however, the problem does need solving.
      • Your only option is to capture the elevated process' output streams in (temporary) files.

      • While Start-Process does offer -RedirectStandardOutput and -RedirectStandardError parameters that accept file paths, they can not be used in your case, because these parameters syntactically cannot be combined with an elevation request (-Verb RunAs), a restriction that is also reflected in the underlying .NET APIs (see this answer for details).

      • Thus, you'll need to capture output from inside the elevated process, by using redirections there, which necessitates switching from a -f (-File)-based to a -c (-Command)-based CLI call.

      • You'll then have to read the target files' content in your VBA code after the fact.

    ' Note the addition of -Wait to the Start-Process call, 
    ' the switch to -c in the nested call, 
    ' the use of & the call operator to invoke the script, the
    ' switch to '...' quoting around the script-file path,
    ' and the > and 2> redirections with sample output file names
    ' stdout.txt and stderr.txt
    strCommand = "pwsh.exe -c Start-Process -Wait -Verb RunAs pwsh.exe \""-ExecutionPolicy Unrestricted -NoExit -c & 'C:\PowerShell Scripts\Disable Network Temporarily.ps1' >stdout.txt 2>stderr.txt\"""
    
    ' ...
    
    ' After synchronous execution (due to -Wait), read the output files, stdout.txt and stderr.txt
    ' Without -Wait, you'd have to track the lifetime of the elevated
    ' process manually.
    

    [1] While your original code therefore had a syntax error, the way pwsh.exe quietly ignored it is actually a bug in the latter, as of PowerShell 7.4.x; a simple repro: pwsh -nop -c "\"hi" (from outside PowerShell; from PowerShell or a POSIX shell, use pwsh -nop -c '"hi'); see GitHub issue #14284.
    However, the failure is - abstractly - reflected in the process exit code, which is set to 1.