I have a script D:\Script.ps1 that runs using a shortcut:
# The shortcut is located in the startup folder $Env:AppData\Microsoft\Windows\Start Menu\Programs\Startup\Script.lnk
# The object in this shortcut description
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -File D:\Script.ps1
How can I see in the console that the process Script is working? How can I mark this process in script or in shortcut description so that I can see if it is working or has already completed the task and completed the work? I could track the process using an ID (Get-Process).ID, but I do not know the ID of my process when starting OS. I can’t Get it's Name easy and Set, as, for example, in Start-Job. I can not distinguish it from other running processes powershell via (Get-Process).ProcessName, they all have the ProcessName of powershell. What can I do to distinguish my powershell process from other powershell running ones? Thanks
If you can switch from using a batch file as a wrapper or can tolerate another PowerShell script to launch the real script. You can use Start-Process with the -PassThru parameter combined with Wait-Process to tell you when it's complete.
Something like:
$Process = Start-Process -FilePath C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe -ArgumentList "-command .{read-host} -WindowStyle Normal" -passthru
$Process | Wait-Process
Write-Host "The process has completed."
However, if you need to observe the process completely apart from it there are a couple of other approaches I can think of.
Get-Process doesn't return a command line property but the WMI Win32_Process class does. You can match some aspect of the CommandLine to differentiate & isolate the process running the script of interest. Something like:
Get-CimInstance win32_Process -Filter "CommandLine LIKE '%d:\\script.ps1'"
You can use this directly or to get the PID or any other characteristic to further observe with. You can certainly get more elaborate with the filter.
Note: Typical best practice is to move the filter criteria to the left, but in this case you might think about using a PowerShell | Where-Object{}, as performance probably wouldn't be a concern. It might make it somewhat easier to filter based on CommandLine. I'd only do that if the existing filter was troublesome.
You can also add the command line column in Task Manager to watch just the same, but without scripting etc...
These aren't exactly eloquent solutions, but let me know if it's helpful.
Related
I trying to install a software using Start-Process in PowerShell, I would like for the script to wait until a command is completed before proceeding to the next one. I'm not experienced I tired the script below but it did not work.
Start-Process -Wait -FilePath "C:\Temp\Latitude_5X10_Precision_3550_1.15.0.exe" -ArgumentList "/S" -PassThru
Your Start-Process call is correct, but -Wait invariably only tracks the lifetime of the directly launched process (C:\Temp\Latitude_5X10_Precision_3550_1.15.0.exe in your case).
That is, you're out of luck if the target process itself spawns yet another process in order to perform its task and then returns before that child process has terminated.
Additional work is then needed, if even feasible:
If you know the name of the child process, you can try to find and track it via Get-Process.
Alternatively, if you know of an indirect sign that the task has completed, such as the existence of a directory or a registry entry, look for that.
As an aside: console(-subsystem) applications can be invoked directly for synchronous (blocking) execution (e.g., foo.exe bar baz or & $fooExePath bar baz), which is the preferred method, because it connects the application's output streams to PowerShell's streams.
I am running multiple PowerShell scripts at once. I would like to be able to wait on certain ones to finish before opening new scripts. Basically, I was thinking if I could find the command line option that ran it something like "powershell.exe -Path "<script dir>" that would do it.
I tried doing a Get-Process | gm to find any parameters that I could call to get that information and I didn't see any (doesn't mean they aren't there) I tried looking through Task Manager to see if I could view something through the gui that I could link to but that didn't help either.
I hope I can get something like
Start-Process -FilePath ".\<script>.ps1" -ArgumentList "<args>"
do
{
sleep 10
}
until ((Get-Process -ProcessName "PowerShell" | where "<paramater>" -EQ ".\<script>")
I need to wait until that process is done but I don't want to put a wait at the end of the Start-Process because after that Start-Process kicks off I need some other items to go to while my .\ is running. I just need it to wait before another section of script kicks off.
Have a look at the "Job" cmdlets https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_jobs?view=powershell-6
And the $PID automatic variable, this will give the process ID of the current PowerShell session.
I've been using the site for a while, searching through the questions and answers, trying to map them to my scenario, but I'm either missing something, or what I'm looking to do isn't possible (at least the way I'm trying to do it), hence I'm hoping for a push in the right direction. Thanks in advance for reading.
I've been working on a fairly sizeable automation project. My main script performs a number of tasks, and generally works well, and reliably. At one stage of the script, I execute another PowerShell script, which was written by another team. I call the script as follows:
.\DeployMySQLProvider.ps1 -AzCredential $asdkCreds `
-VMLocalCredential $vmLocalAdminCreds `
-CloudAdminCredential $cloudAdminCreds
-PrivilegedEndpoint $ERCSip `
-DefaultSSLCertificatePassword $secureVMpwd -AcceptLicense
When I call it this way, from my main script, it works fine, however, this script uses and registers a DLL file during it's deployment, and locks it until the PowerShell window and session is closed. At the end of my main script, I have a cleanup phase, which can't complete it's job because of this locked DLL.
My thoughts therefore, were to launch the 2nd script into a new PowerShell window and session, either using Start-Process or Invoke-Expression, but I just can't seem to get either right. Most of the variables I'm passing through to the 2nd script aren't just strings, which is probably where I'm falling over. They are a mix of usernames and passwords (secure strings) along with $ERCSip which is a string.
Should I be looking at Start-Process / Invoke-Expression, or something else entirely? When I was testing with Start-Process, I had the following defined, but couldn't get the ArgumentList side working correcly for me (blank below):
Start-Process "$pshome\powershell.exe" -PassThru -Wait `
-Verb RunAs -ErrorAction Stop -ArgumentList ""
Any pointers in the right direction would be much appreciated.
Thanks!
I've used something similar to this in my scripting:
$scriptpath="c:\pathto\deploysqlProvider"
$a = "$scriptpath\DeployMySQLProvider.ps1 -AzCredential $asdkCreds `
-VMLocalCredential $vmLocalAdminCreds `
-CloudAdminCredential $cloudAdminCreds
-PrivilegedEndpoint $ERCSip ` "
-DefaultSSLCertificatePassword $secureVMpwd -AcceptLicense
Start-Process -Verb runas -FilePath powershell.exe -ArgumentList $a -wait -PassThru ;
Not sure if you need it to runas admin or not (-verb runas).
I'd suggest you then look for the Powershell process and path. So that if you have to kill this separate process you can.
Consider the powershell command:
cmd.exe "/c start notepad.exe"
Using powershell.exe (console) this command completes immediately after starting the cmd/notepad process and does not wait for notepad/cmd to exit. If I want it to wait for completion I have to pipe the output to Out-Null.
Using the powershell ISE this command blocks execution until notepad/cmd is closed.
Further, if I use create a new process (of powershell.exe) in a script running in powershell.exe using System.Diagnostics.Process and redirect standard output the script now blocks execution until notepad/cmd is closed. If I don't redirect output it does not block execution.
But if I use c# to create a new process with the same settings/start info, and run the same script with redirected output it doesn't block execution.
I'm at a loss here. Obviously it has something to do with the setup of the execution and output and maybe "cmd.exe". I'm hoping someone understands what's fundamentally happening behind the scenes differently in these cases. I know the ISE and cmd.exe isn't fully supported but the I don't know why the other 3 aren't consistent.
Why do the scripts not run with the same behavior (especially the powershell console ones) and how do I get them to?
Edit:
After some troubleshooting I was able to get all the powershell.exe versions to run the same (the ISE isn't of importance to me). The odd ball where cmd.exe "/c start notepad.exe" blocks execution is when a new process is created in powershell to run powershell.exe using System.Diagnostics.Process. If output is redirected (the reason I was using System.Diagnostics.Process in the first place, Start-Process doesn't support redirecting except to a file) then calling WaitForExit() on the process object causes the blocking to occur.
Simply substituting WaitForExit() with Wait-Process (and the process ID) causes the powershell script running in the new powershell.exe to execute as expected and exit after running the command. Hopefully this information combined with #mklement0 answer will be sufficient for anyone else having similar problems.
To get predictable behavior in both the regular console and in the ISE:
Start a GUI application asynchronously (return to the prompt right away / continue executing the script):
notepad.exe
Invoking Notepad directly makes it run asynchronously, because it is a GUI-subsystem application.
If you still want to track the process and later check whether it is still running and what its exit code was on termination, use -PassThru, which makes Start-Process return a [System.Diagnostic.Process] instance:
$process = Start-Process -PassThru notepad.exe
$process.HasExited later tells you whether the process is still running.
Once it has exited, $process.ExitCode tells you the exit code (which may not tell you much in the case of a GUI application).
To wait synchronously (at some point):
Use Wait-Process $process.ID to wait (indefinitely) for the process to terminate.
Add a -Timeout value in seconds to limit the waiting period; a non-terminating error is reported if the process doesn't terminate within the timeout period, causing $? to reflect $False.
Start a GUI application synchronously (block until the application terminates):
Start-Process -Wait notepad.exe
-Wait tells Start-Process to wait until the process created terminates; this is the equivalent of cmd /c 'start /wait notepad.exe'.
There's a non-obvious shortcut to Start-Process -Wait: you can repurpose the Out-Null cmdlet to take advantage of the fact that piping invocation of a program to it makes Out-Null to wait for the program's termination (a GUI application has no stdout or stderr output, so there's nothing for Out-Null to discard; the only effect is synchronous invocation):
notepad.exe | Out-Null
In fact, this approach has two advantages:
If arguments must be passed to the GUI application, they can be passed directly, as usual - rather than indirectly, via Start-Process's -ArgumentList parameter.
In the (rare) event that a GUI application reports a meaningful process exit code (e.g, msiexec.exe), the Out-Null approach (unlike Start-Process -Wait) causes it to be reflected in the automatic $LASTEXITCODE variable.
Note: In rare cases, a GUI application may explicitly attach to the caller's console and write information to it; in order to surface that, pipe to | Write-Output instead (you'll still be able to evaluate $LASTEXITCODE) - see this answer.
Note that for console-subsystem applications (e.g., findstr.exe), synchronous execution is the default; Start-Process is only needed for GUI applications (and for special situations, such as wanting to run an application in a new console window or with elevation (run as admin)).
To run a console application or shell command asynchronously (without opening a new console window), you have the following options:
[Preferred] Use Start-Job kick off the command, and Receive-Job to receive its output / success status later.
$j = Start-Job { sleep 2; 'hi' }
To synchronously wait for this job to finish (and remove it automatically), use
Receive-Job -Wait -AutoRemoveJob $j
In PowerShell (Core) 6+:
You can use the simpler ... & syntax (as in
POSIX-like Unix shells such as bash) in lieu of Start-Job; e.g.:
$j = & { sleep 2; 'hi!' } &
Better yet, you can use the lighter-weight, faster Start-ThreadJob cmdlet, which uses threads for concurrency, but otherwise seamlessly integrates with the other *-Job cmdlets (note that it has no short-hand syntax):
$j = Start-ThreadJob { sleep 2; 'hi' }
[Not advisable] You can use something like Start-Process -NoNewWindow powershell -Args ..., but it is ill-advised:
Passing a shell command to powershell via Start-Process requires intricate quoting based on obscure rules - see this answer of mine for background information.
Any stdout and stderr output produced by the application / shell command will by default arrive asynchronously in the current console, interspersed with what you're doing interactively.
While you can redirect these streams to files with RedirectStandardOutput and -RedirectStandardError (and stdin input via -RedirectStandardInput) and you can use -PassThru to obtain a process-information object to determine the status and exit code of the process, Start-Job and Receive-Job are a simpler, more convenient alternative.
P.S.: I don't have an explanation for why cmd.exe "/c start notepad.exe" is blocking in the ISE but not in the regular console. Given the solutions above, however, getting to the bottom of this discrepancy may not be needed.
I was struggling to get this simple (?) function working today.
I have a PowerShell script that reads computer names from a txt-based file. It works fine when run from a PowerShell session by the following one-liner:
./"Server Health Check.ps1" -List One-off.txt
As you can see, it's got a long file name, so it's wrapped with quotes.
However, I'm building a PowerShell GUI form with radio boxes that will pass on a choice for a text file that will be used to a call the script. Trick is, the script needs to be run with alternate admin account, and i'm not clear how to make that work.
For another script I've got that doesn't use I know i can use something along the lines of the following, this uses the old DOS "runas", however, it doesn't work with the -list function.
invoke-command -scriptblock {runas.exe /user:domain\$Env:Username"admin" "powershell.exe -file \"\\Server\c$\LONG FOLDER\Server Health Check.PS1""}
So, in a nutshell, how do get a script to launch with alternate credentials that reads a parameter (-List) from the command line? I'm also keen to preserve my directory structure, which includes folders with spaces. The script is titled: "Server health check.ps1"
The last thing I tried was the following
$ScriptPath = "C:\SCRIPTS FOLDER\Server Health Check.ps1"
$ArgList = "-List C:\SCRIPTS FOLDER\One-off.txt"
Invoke-Command -filepath $ScriptPath -Credential DragonBallDomain\$Env:UserName"Admin" -ArgumentList $ArgList
The result was the following message:
Invoke-Command : Parameter set cannot be resolved using the specified named parameters.
I'm almost certain this is do-able by invoke-command or start-process, it's just a matter of getting the correct formatting? I'm probably missing a / or a ' or "" somewhere in my trials with start-process or invoke-command.
Any help appreciated!
Update for April 30:
I've tried some more to make this work, i'm close, but still not quite there.
$LongScriptPath = resolve-path Script.ps1
$LongFolderPath = \\UNC\PATH TO FOLDER\WITH LONG NAME\
start-process -filepath powershell.exe -argumentlist " -file``"$($FilePath.path)`"" -cred DOMAIN\USERID -WorkingDirectory "$LongFolderPath"
Adding the -credential is what causes an error that states that the -file parameter is invalid. I'm sure there's a way to do this.
Note: Completely rewritten after the requirements became clearer.
To run a command as a different user locally, use Start-Process -Credential ...
That is what you've attempted in your update in principle, but there are problems with how you're passing parameters; try this instead:
$LongScriptPath = resolve-path Script.ps1
$LongFolderPath = '\\UNC\PATH TO FOLDER\WITH LONG NAME\'
start-process `
powershell.exe `
-ArgumentList '-file', $LongScriptPath, '-List', 'One-off.txt' `
-Credential DOMAIN\USERID `
-WorkingDirectory $LongFolderPath
The key to making this work is to pass all parameters to pass to powershell.exe as an array via Start-Process's -ArgumentList parameter, which means that the parameters must be ,-separated.
Note how an array is always parsed in expression mode, which means that literal string elements such as -file and -List must be quoted.
It is important in general to understand the difference between PowerShell's two fundamental parsing modes, argument mode and expression mode, and which is applied when - see https://technet.microsoft.com/en-us/library/hh847892.aspx
Add -Wait to wait for the script to finish; Start-Process is asynchronous by default (all PS cmdlets named Start-* are).
Caveat: For commands invoked as a different user, you can only wait from an elevated prompt.
If it isn't, the command will still execute, but will do so asynchronously, and you'll get an Access denied error message in the current console; in effect, -Wait is ignored.
Only if not running as a different user: Add -NoNewWindow -Wait if you want to run the script in the current console window; Start-Process opens a new window by default for console applications such as powershell.exe and cmd.exe.
If you do run the command as a different user, -NoNewWindow is quietly ignored.
As for the original symptom and why using Invoke-Command to run a command locally as a different user is ill-advised:
Invoke-Command -Credential ... requires that the -ComputerName parameter be specified too.
Run Get-Help Invoke-Command to see all parameter sets that involve the -Credential parameter. The OP's original command had only -Credential, but not -ComputerName, which caused PS to complain that no parameter set could be unambiguously identified.
Once you use -ComputerName, PowerShell remoting is invariably used, even if you specify . - the local computer - as the only computer to target.
Using remoting has two implications:
Remoting is not available by default, and must be configured on the target computer (the local computer, in this case).
Using remoting requires invocation with admin privileges.
In short:
While you can perform purely local invocations with Invoke-Command, you cannot do so as another user, because that invariably involves remoting.
Start-Process, by contrast, solely exists to run commands locally, optionally as a different user.