Question:
I am trying to use a VBScript helper script to launch a PowerShell script in a hidden window. I have been doing this successfully for some time, but now I want to add getting a return code, and that makes things more complicated.
The usual approach for getting a return code is to use .Exec rather than .Run, but then you lose the ability to hide the command window. I found this thread, and based on that and a known working strCMD
, I tried
1 2 3 |
strCMD = strCMD & " > c:\out.txt" objShell.Run strCMD, 0, True |
But that doesn’t work. Any suggestions where I am going wrong? Or suggestions on a better way to get a hidden Powershell script with return code? FWIW, I have to target PS 2.0. 🙁
For completeness, the value of strCMD is
1 2 |
powershell -noLogo -noProfile -file "C:\Program Files\PragmaticPraxis\Resources\TestMessage.ps1" -message:"I'M ALIVE!!!" -message2:"Me too!" |
And TestMessage.ps1 is simply
1 2 3 4 5 6 7 8 9 10 |
[CmdletBinding()] param ( [string]$message, [string]$message2 ) [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") > $null [System.Windows.Forms.MessageBox]::Show($message) [System.Windows.Forms.MessageBox]::Show($message2) |
EDIT:
Thanks to omegastripes’ link, I am trying to implement a more general and PowerShell focused version of this implementation, with some code refactor to eliminate the one time calls to functions and awkward forced exits. What I have is this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Option Explicit Dim strCmd, strRes, objWnd, objParent, strSignature If WScript.Arguments.Named.Exists("signature") Then For Each objWnd In CreateObject("Shell.Application").Windows If IsObject(objWnd.getProperty(WScript.Arguments.Named("signature"))) Then Exit For Next Set objParent = objWnd.getProperty(WScript.Arguments.Named("signature")) objWnd.Quit objParent.strRes = CreateObject("WScript.Shell").Exec(objParent.strCmd).StdOut.ReadAll() Else strCmd = "powershell.exe -noLogo -noProfile -executionPolicy bypass -file ""\\Mac\Px\Support\Px Tools\Dev 3.3.#\_Spikes\TestMessage.ps1""" strSignature = Left(CreateObject("Scriptlet.TypeLib").Guid, 38) GetObject("new:{C08AFD90-F2A1-11D1-8455-00A0C91F3880}").putProperty strSignature, Me CreateObject("WScript.Shell").Run ("""" & Replace(LCase(WScript.FullName), "wscript", "cscript") & """ //nologo """ & WScript.ScriptFullName & """ ""/signature:" & strSignature & """"), 0, True WScript.Echo strRes End If |
Which in turn calls this PS1
1 2 3 4 5 6 7 8 |
try { [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") > $null [System.Windows.Forms.MessageBox]::Show("Message test!") > $null Write-Host "Success" } catch { Write-Host "Failure" } |
This works in that it does run the PS1, and it is hidden, but I am having trouble getting results back. I have tried Return, Write-Output and Write-Host and none sends the results from the PS1 back to the VBS. I am also having some issues when passing arguments, and I really don’t like the For Each loop with a forced Exit internally, but that is polish. The bigger issue is how to consistently get results back from the PS1.
Answer:
Your powershell script needs to return an exit code through
1 2 |
Exit |
Your WScript.Shell’s Run method in VBS will then return this code so you need to save it.
1 2 3 |
Set WshShell = WScript.CreateObject("WScript.Shell") Result = WshShell.Run(... |
So Result now contains the Exit code so you can now either use it or return it again and exit the process.
1 2 |
WScript.Quit(Result) |