Powershell Console output not available - vba

I am trying to run a pwershell script from excel, which i can do and it works, but the powershell window does not show the information from the write-host comands in the script. If i run the file from the cmd prompt i get all the write-host texts appearing in the console window, like below.
Powershell run from command prompt
[]
However if i use this code from excel.
Sub RunAndGetCmd()
strCommand = "Powershell -File ""C:\PSFiles\TestOutput.ps1"""
Set WshShell = CreateObject("WScript.Shell")
Set WshShellExec = WshShell.Exec(strCommand)
End Sub
I get no texts shown in the console window, just the a flashing cursor like the following
Same Powershell ran from excel
I know the script runs and dooes everything, but i would like to output the texts as the script runs so i can see how far it has gotten.
Any ideas greatly appreciated.

The WScript.Shell Exec command redirects the stdin, stdout and stderr streams so you can access them from your application. As a result, anything written to stdout by the external application (e.g. using write-host in PowerShell) gets redirected instead of being displayed in the external application's window.
If you want the output displayed in the application's window you can use the Run method instead - e.g.
Option Explicit
Sub RunAndGetCmd()
Dim strCommand As String
Dim WshShell As Object
strCommand = "Powershell -File ""C:\PSFiles\TestOutput.ps1"""
Set WshShell = CreateObject("WScript.Shell")
WshShell.Run strCommand
End Sub
but if you're doing that, you might as well just use the built-in VBA Shell function:
Option Explicit
Sub RunAndGetCmd()
Dim strCommand As String
strCommand = "Powershell -File ""C:\PSFiles\TestOutput.ps1"""
VBA.Shell strCommand
End Sub
Note also that you might want an -ExecutionPolicy RemoteSigned in your command in case the machine has it set to Restricted:
strCommand = "Powershell -ExecutionPolicy RemoteSigned -File ""C:\PSFiles\TestOutput.ps1"""

Related

How can I run a a Powershell command in VBA which contains "$env:UserName"?

I have the following macro which works just fine:
Private Sub Macro()
Dim ExecuteCommand As String
ExecuteCommand = "PowerShell -Command ""& 'C:\Program Files\R\R-4.2.2\bin\Rscript.exe' 'C:\Users\MyUser\Cool Calculations.R'"""
Shell ExecuteCommand
End Sub
What I want to do however, is to replace the name "MyUser" with $env:UserName instead.
However this macro does not seem to work:
Private Sub Macro()
Dim ExecuteCommand As String
ExecuteCommand = "PowerShell -Command ""& 'C:\Program Files\R\R-4.2.2\bin\Rscript.exe' 'C:\Users\$env:UserName\Cool Calculations.R'"""
Shell ExecuteCommand
End Sub
I have tried to run the command manually in PowerShell and it works correctly:
& "C:\Program Files\R\R-4.2.2\bin\Rscript.exe" "C:\Users\$env:UserName\Cool Calculations.R"
I am not sure what I am doing wrong when trying to add $env:UserName in my macro?
As Mathias points out, only "..." strings (expandable strings) perform variable expansion (interpolation) in Powershell; '...' strings (verbatim strings) do not.
Thus you must pass what PowerShell ultimately see as a "C:\Users\$env:UserName\Cool Calculations.R" argument (as an aside: consider simplifying to "$env:USERPROFILE\Cool Calculations.R"); since you're calling from VBA, this requires:
Using ""..."" to embed " chars. inside a VBA "..." string...
and \-escaping for the sake of powershell.exe, the Windows PowerShell CLI (which receives its -Command argument as a "..." string, so any " chars. to be retained as part of the command must be escaped)
That is, you need something like the following (sic; spaces added for readability)
" PowerShell -Command "" & '...' \""...\"" "" "
Specifically:
Private Sub Macro()
Dim ExecuteCommand As String
ExecuteCommand = "PowerShell -Command ""& 'C:\Program Files\R\R-4.2.2\bin\Rscript.exe' \""C:\Users\$env:UserName\Cool Calculations.R\"""""
Shell ExecuteCommand
End Sub
mklement0's response is very elegant, tested working. But I tested the command without escape, that seems not required under Windows Powershell, as Mathias suggested, it works also. I replaced Rscript.exe that I have not installed, by Get-ChildItem cmdlet like this:
Sub RunPSFromVba()
Dim ExecuteCommand As String
'ExecuteCommand = "PowerShell -Command ""& 'C:\Program Files\R\R-4.2.2\bin\Rscript.exe' 'C:\Users\$env:UserName\Cool Calculations.R'"""
ExecuteCommand = "PowerShell -NoExit -Command ""& 'Get-ChildItem' ""C:\Users\$env:UserName\Downloads"""""
Shell ExecuteCommand
End Sub
With PowerShell -NoExit, I keep the PowerShell Window alive after execution, I got this screenshot:
Hope this will also help.

manual entry in cmd window works, VBA executing CMD works, but not VBA when I use run (so I can hide the window)

SECOND EDIT/UPDATE: tried the path change recommendations, did not see any changes to the command string, still does not work. I re-wrote the code to use a fixed text file instead of a random temp file so I could monitor the contents of the file during execution. Able to conclusively show it is the
oShellObject.Run sCommandStringToExecute & " > " & sShellRndTmpFile, 0, True
code line that doesn't behave as expected. Still works with the w32tm command line, but not with the ntpq command line. With ntpq command, no changes made to the file, no error flags. I also tried out (again) the exec version of this problem where the window is supposed to flash a bit before it gets hidden programmatically. I get the expected reslut using exactly the same command string, cut and pasted into the other code. So the same command line works with manual entry into CMD, into PowerShell, and in the .exec code version, not the .run code version.
End of second edit. -------------------
EDIT: more debugging... ntpq -p works if I do .exec instead of .run, but then of course can't hid the cmd window. Extra test code at the end.
This Works: If I run these two commands in manually opened cmd window, or PowerShell window, both give the expected results.
w32tm /stripchart /computer:time.nist.gov /dataonly /samples:3 /rdtsc /period:1
ntpq -p
The second, ntpq -p, is bundled with NTP windows software from the home of the Network Time Protocol project that gives similar information to windows' w32tm when NTP is set up to look at the same time service computer as in the w32tm command.
This Doesn't work:
When I try to use these two command string when running CMD functions hidden using the classic "write to file" method shown in SO here and other places, the w32tm version gives the same results as the manual version, but the ntpq version just returns "error".
I read every single one of the recommended links for this question as well as searching OS and Google, and have not found an answer.
I am stuck on next step to troubleshoot the problem...only thing I could think of was to run the commands manually to confirm they work there. I can't imagine it being a administrator privileges issue since I can run them both in CMD line or PowerShell windows opened at normal rights level.
What should I look at next?
Here is the test code.
Option Explicit
Sub TestShellRun()
Dim sCmd As String, sReturnNTP As String
sCmd = "w32tm /stripchart /computer:time.nist.gov /dataonly /samples:3 /rdtsc /period:1 " ' /packetinfo"
sCmd = "%ComSpec% /C %SystemRoot%\system32\" & sCmd
sReturnNTP = fShellRun(sCmd) 'good return value, same as manual cmd line
Debug.Print sReturnNTP
sCmd = "ntpq -p"
sCmd = "%ComSpec% /C %SystemRoot%\system32\" & sCmd
sReturnNTP = fShellRun(sCmd) 'ERROR return value, even though manual cmd line has good values
Debug.Print sReturnNTP
End Sub
Public Function fShellRun(sCommandStringToExecute) As String
' This function will accept a string as a DOS command to execute.
' It will then execute the command in a shell, and capture the output into a file.
' That file is then read in and its contents are returned as the value the function returns.
' "myIP" is a user-selected global variable
Dim oShellObject, oFileSystemObject, sShellRndTmpFile
Dim oShellOutputFileToRead
Dim iErr As Long
Set oShellObject = CreateObject("Wscript.Shell")
Set oFileSystemObject = CreateObject("Scripting.FileSystemObject")
sShellRndTmpFile = oShellObject.ExpandEnvironmentStrings("%temp%") & oFileSystemObject.GetTempName
On Error Resume Next
oShellObject.Run sCommandStringToExecute & " > " & sShellRndTmpFile, 0, True
iErr = Err.Number
On Error GoTo 0
If iErr <> 0 Then
fShellRun = "error"
Exit Function
End If
On Error GoTo err_skip
fShellRun = oFileSystemObject.OpenTextFile(sShellRndTmpFile, 1).ReadAll
oFileSystemObject.DeleteFile sShellRndTmpFile, True
Exit Function
err_skip:
fShellRun = "error"
oFileSystemObject.DeleteFile sShellRndTmpFile, True
End Function
sCommand = "ntpq.exe -p"
Set WshShell = CreateObject("WScript.Shell")
Set WshShellExec = WshShell.Exec(sCommand)
strOutput = WshShellExec.StdOut.ReadAll
Debug.Print strOutput
Your fShellRun function didn't work due to error in temporary file path. Here is fixed version.
Function fShellRun(sCommandStringToExecute) As String
...
'invalid file path without path separator between directory path and filename!
sShellRndTmpFile = oShellObject.ExpandEnvironmentStrings("%temp%") & _
oFileSystemObject.GetTempName
'valid path with path separator between directory path and filename
sShellRndTmpFile = oFileSystemObject.BuildPath( _
Environ("temp"), oFileSystemObject.GetTempName)
...
End Function

vba shell command executing but no output

I am having some problems running a shell command and checking the output of the data. I wish to check using vba if the current remote user of the DB is Active. In
command prompt =
for /f "tokens=1-8" %a in ('quser') do #if "%d"== "Active" echo %COMPUTERNAME% %a %d
returns the users logged on and their state I wish to check that none of them are disconnected ("Disc"). I used this function to check the shell and return the pipe value as a string in a message box
Public Function ShellRun(sCmd As String) As String
'Run a shell command, returning the output as a string'
Dim oShell As Object
Set oShell = CreateObject("WScript.Shell")
'run command'
Dim oExec As Object
Dim oOutput As Object
Set oExec = oShell.Exec(sCmd)
Set oOutput = oExec.StdOut
Debug.Print sCmd
'handle the results as they are written to and read from the StdOut object'
Dim s As String
Dim sLine As String
While Not oOutput.AtEndOfStream
sLine = oOutput.ReadLine
If sLine <> "" Then s = s & sLine & vbCrLf
Wend
ShellRun = s
'example MsgBox ShellRun("cmd.exe /c" & "dir c:\")
End Function
Call Command used on click event
Dim CMDLineCommand As String
CMDLineCommand = "for /f ""tokens=1-8"" %a in ('quser') do #if ""%d""== ""Active"" echo %COMPUTERNAME% %a %d"
'(CMDLineCommand = "dir c:\")<------ THIS WORKS FINE
MsgBox ShellRun("cmd.exe /c " & CMDLineCommand)
This works fine for loads of command line commands I have tested it with but not query and therefore query user. The query user command works fine from command line but does not return anything when issued through a VBA Shell commands.
Any thoughts would be appreciated.
because shell does not know the path of the query.exe(quesr) it does not continue where as command prompt can use system variables to find exe's. solution find the query.exe and copy it to a working directory then run the shell command. mine was located in a hashed folder within C:\Windows\WinSxS be careful as here are 64bit versions and 32 bit.

Execute a command in command prompt using excel VBA

I have a fixed command which i need to pass to command prompt using VBA and then the command should run.
e.g. "perl a.pl c:\temp"
following is the command i am trying to use but it just opens command prompt and doesn't run the command.
Call Shell("cmd.exe -s:" & "perl a.pl c:\temp", vbNormalFocus)
Please check.
The S parameter does not do anything on its own.
/S Modifies the treatment of string after /C or /K (see below)
/C Carries out the command specified by string and then terminates
/K Carries out the command specified by string but remains
Try something like this instead
Call Shell("cmd.exe /S /K" & "perl a.pl c:\temp", vbNormalFocus)
You may not even need to add "cmd.exe" to this command unless you want a command window to open up when this is run. Shell should execute the command on its own.
Shell("perl a.pl c:\temp")
-Edit-
To wait for the command to finish you will have to do something like #Nate Hekman shows in his answer here
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
Dim waitOnReturn As Boolean: waitOnReturn = True
Dim windowStyle As Integer: windowStyle = 1
wsh.Run "cmd.exe /S /C perl a.pl c:\temp", windowStyle, waitOnReturn

Running powershell scripts from within a .NET windows app

I'm needing to run scripts from within a vb.net windows app.
I've got the scripts running in the background fine;
Using MyRunSpace As Runspace = RunspaceFactory.CreateRunspace()
MyRunSpace.Open()
Using MyPipeline As Pipeline = MyRunSpace.CreatePipeline()
MyPipeline.Commands.AddScript("import-module -name " & moduleName &
vbCrLf &
"(get-module -name " & moduleName & ").version")
Dim results = MyPipeline.Invoke()
'Do something with the results
End Using
MyRunSpace.Close()
End Using
However, i now need to be able to have the powershell run (not in the background) eg. When prompts occur;
Set-ExecutionPolicy unrestricted
I'm currently looking into the Microsoft.PowerShell.ConsoleHost namespace to see if i can use something like;
Dim config = RunspaceConfiguration.Create
ConsoleShell.Start(config, "Windows PowerShell", "", New String() {""})
Can anyone advise me please???
EDIT: I've fudged it a bit with this;
Public Function RunPowershellViaShell(ByVal scriptText As String) As Integer
Dim execProcess As New System.Diagnostics.Process
Dim psScriptTextArg = "-NoExit -Command ""& get-module -list"""
'Dim psScriptTextArg = "-NoExit -Command ""& set-executionPolicy unrestricted"""
'Dim psScriptTextArg = ""-NoExit -Command """ & scriptText & """"
execProcess.StartInfo.WorkingDirectory = Environment.SystemDirectory & "\WindowsPowershell\v1.0\"
execProcess.StartInfo.FileName = "powershell.exe"
execProcess.StartInfo.Arguments = psScriptTextArg
execProcess.StartInfo.UseShellExecute = True
Return execProcess.Start
End Function
But there's gotta be a better way??
There is a distinction between the PowerShell engine and its host. What you're wanting is to run the engine within your application but then fire up a separate host (which also is hosting the PowerShell engine) to handle prompts. You might want to look into modifying your application to act as a host itself. You could then react to prompts (read-host) and pop dialog boxes or whatever. Take a look at this relevant PowerShell namespace. Also check out this blog post on creating a simple PSHost.