1

I am attempting to use the updated technique omegastripes provides here to crate a helper script for silently launching PowerShell and getting results/status back. The first thing I did was refactor to help me understand the logic. Basically I got rid of the two functions, one with a forced exit, and moved them into an If/Then/Else. I also moved all everything into the Conditional when searching for the identified window. So I went from this:

Dim strCmd, strRes, objWnd, objParent, strSignature

If WScript.Arguments.Named.Exists("signature") Then WshShellExecCmd
strCmd = "%comspec% /c tasklist"
RunCScriptHidden
WScript.Echo strRes

Sub RunCScriptHidden()
    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
End Sub

Sub WshShellExecCmd()
    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()
    WScript.Quit
End Sub

To this:

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 
            Set objParent = objWnd.getProperty(WScript.Arguments.Named("signature"))
            objWnd.Quit
            objParent.strRes = CreateObject("WScript.Shell").Exec(objParent.strCmd).StdOut.ReadAll()
            Exit For
        End If
    Next
Else
    strCmd = "%comspec% /c tasklist"
    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

That works great, and my only question there is just a critique of the refactor. Is there anything that could be improved, and indeed, is there any argument against the refactor in the first place?

From there, I revised the strCmd to

strCmd = "powershell.exe -noLogo -noProfile -executionPolicy bypass -file ""\\Mac\Px\Support\Px Tools\Dev 3.3.#\_Spikes\TestMessage.ps1"" -message:""It's ALIVE!"""

Which calls this PowerShell

param (
    [string]$message
)
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") > $null
[System.Windows.Forms.MessageBox]::Show($message) > $null

So far, so good. Now to get status back from the PowerShell. And here I should be clear, the ultimate goal is a generic launcher where I want to be able to catch any errors, pass them back to the VBS, and from there write to an error log or event. Some of the potential errors include things like passing a parameter that doesn't exist in the target PS1. So, getting back to returning info, I have tried:

Write-Output "Write-Output"
Write-Host "Write-Host"
Out-Host "Out-Host"

None of these actually gets returned. So, how can I output to Standard Output such that the VBS can access that info? I THINK one aspect is that the original strCmd emits Standard output as it processes, whereas PowerShell is going to output at the end. But that's just a guess at the issue, so rather than cluttering this with more experiments I will ask, is this possible, and if so, what changes are needed?

Gordon
  • 6,257
  • 6
  • 36
  • 89
  • How I've worked around this in the past is writing output to a text file and then just reading that textfile back in via vbscript. – Nathan Rice Dec 04 '18 at 21:23

1 Answers1

0

This is an improved version of the code you linked to, invoking cmd.exe from vbs. I did it today. Splits the confused responses from cmd.exe into an array instead of putting everything into a hard-to-parse string.

In addition, if an error occurs during the execution of cmd.exe, a message about its occurrence will become known in vbs.

Well ... powershell:

Option Explicit
Sub RunCScriptHidden()
    strSignature = Left(CreateObject("Scriptlet.TypeLib").Guid, 38)
    GetObject("new:{C08AFD90-F2A1-11D1-8455-00A0C91F3880}").putProperty strSignature, Me
    objShell.Run ("""" & Replace(LCase(WScript.FullName), "wscript", "cscript") & """ //nologo """ & WScript.ScriptFullName & """ ""/signature:" & strSignature & """"), 0, True
End Sub
Sub WshShellExecCmd()
    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() 'simple solution
    Set exec = CreateObject("WScript.Shell").Exec(objParent.strCmd)
    While exec.Status = WshRunning
        WScript.Sleep 20
    Wend
    Dim err
    If exec.ExitCode = WshFailed Then
        err = exec.StdErr.ReadAll
    Else
        output = Split(exec.StdOut.ReadAll,Chr(10))
    End If
    If err="" Then
        objParent.strRes = output(UBound(output)-1) 'array of results, you can: output(0) Join(output) - Usually needed is the last
    Else
        objParent.wowError = err
    End If
WScript.Quit
End Sub
Const WshRunning = 0,WshFailed = 1:Dim cmdExeString,objShell
Dim strCmd, strRes, objWnd, objParent, strSignature, wowError, output, exec

Set objShell = WScript.CreateObject("WScript.Shell"):wowError=False
Dim psCode:psCode="Write-Host 'Hello PS'"
cmdExeString="for /f ""usebackq delims="" %a in (`C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command """ & psCode & """`) do echo %a"
strCmd = "%comspec% /c " & cmdExeString
If WScript.Arguments.Named.Exists("signature") Then WshShellExecCmd
RunCScriptHidden
If wowError=False Then
    objShell.popup(strRes)
Else
    objShell.popup("Error=" & wowError)
End If
Garric
  • 591
  • 3
  • 10