This question was asked a few times before but I could not find a solution to my scenario in any of them.
Basically I need the script to continue after it reboots if needed. It will check a few registry keys and will determine if the machine needs to be rebooted.
I tried using 'workflow' in various ways but could not get it to work successfully.
Here is the rough description of my code:
function (check-if-computer-needs-a-reboot){
if(this and that)
try{
return $true
}
catch{}
return $false
}
if(check-if-computer-needs-a-reboot = $true){
Write-Host "The machine is rebooting..."
Restart-Computer -Wait -Force
Write-Host "The machine was successfully rebooted"
}
else
{
Write-Host "No pending reboot"
}
Hoping the wizards of Stack-overflow can help.
Any help will be greatly appreciated!!!
To continue doing something after a reboot, you need to add a value to the Runonce registry key to specify what to do after the reboot.
Break break your script into two parts (Preboot and Postboot). Put the following at the end of Preboot.ps1:
if(check-if-computer-needs-a-reboot){
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Runonce" /V "Postboot" /d "\"%~dpPostboot.ps1\" MyParameters" /t REG_SZ /f
Write-Host "The machine is rebooting..."
Restart-Computer -Wait -Force
}
# Fall through if reboot was not needed, so continue with part 2
Write-Host "No pending reboot... continuing"
"%~dpPostboot.ps1"
Notes:
I copied this from a .bat file. In a .bat file "%~dp0" means "the drive and path that the current script is running from". The syntax in Powershell might be a little different.
While Powershell can run other programs such as REG.EXE, Powershell has its own built-in HKLM: PSdrive. That should be more efficient than using REG.EXE. I leave it to you to do the conversion.
In your code, you test if a Boolean value "equals" $True. This comparison is never necessary unless the value isn't really Boolean and could be something other than True or False. Just test the Boolean value itself as I've shown above.
Related
I have automated some of my backup routines using power-shell scripts. What can I do to make sure the console stays open for maybe 5 seconds after execution completes so that I can read whatever feedback is provided and verify that there weren't any unexpected errors?
The following will work in most cases, but, as mentioned in this answer, it does not work in Windows PowerShell ISE.
function Pause {
Write-Host -NoNewLine 'Press any key to continue . . .';
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown');
}
This closely mimics the the PAUSE command in the Windows command prompt. Simply place the following line where ever you want the code to pause for the user to view information.
Pause
I'm trying to run below code in an automated scheduled task.
Whether I run this task manually or automated it is not working. When the option 'Run only when user is logged in' is set I at least see a PowerShell window opening, and I do see the jobs getting started. However, when the PS window closes the jobs are not visible (not completed, failed, nothing).
The logging shows the script runs till the import-csv command. I have put the CSV in the C: map, and I run the automated task as the logged in user and on highest privilege.
Why doesn't it get past import-csv? When I run this script in i.e Powershell ISE it works like a charm.
Running program
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
Arguments:
–NoProfile -ExecutionPolicy Unrestricted -File "C:\Users\usr\Desktop\Scripts\script.ps1"
Start-in:
C:\Users\usr\Desktop\Scripts
Write-Host "Starting script"
$maxItems = 8
$iplist = import-csv "C:\Create.csv.txt"
Write-Host "Opened $($iplist[0])"
For ($i=0; $i -le $maxItems; $i++) {
Write-Host $iplist[$i].DisplayName
Start-Job -ScriptBlock {
Param($displayName)
try{
Start-Transcript
Write-Host "Found and started a job for $($displayName)"
Stop-Transcript
}
Catch{
Write-Host "Something went wrong "
Stop-Transcript
}
} -ArgumentList $iplist[$i].DisplayName
}
UPDATE:
The PS window closed before it got to do anything. The answer in this page send me in the right direction. The full fix I used to get this working:
Task Scheduling and Powershell's Start-Job
First, to prevent the powershell window from closing, run add the following line to the bottom of the script:
Read-Host 'Press Any Key to exit'
Second, if you run into issues with params, try explicitly naming the param with a flag:
$iplist = Import-csv -LiteralPath "C:\Create.csv.txt"
Third, make sure that you explicitly declare the delimiter being used if different than a comma.
So I currently have a Powershell script that installs various software silently
What I have at the moment are all of the commands which work perfectly:
Start-Process 'C:\Files\Install files\Firefox' -ArgumentList "/S" -Wait
Start-Process 'C:\Files\Install files\Office' -ArgumentList "/s" -Wait
Start-Process 'C:\Files\Install files\Zeb' -ArgumentList "/S" -Wait
What I now want to implement is 'checking if the installation is succeeded'.
I start the program by checking the install files:
Write-Host "Checking if install files are present..."
if(Test-Path 'C:\Files\Install files\')
{
Write-Host "Files located. Installation will begin"
Write-Host ""
} else
{
Write-Host "Files not located, please check the directory"
Write-Host ""
break
}
And since the code of above is working I thought that I could maybe to the same for installing my software? But using the software directory as a test-path function:
Write-Host "Installing Firefox"
Start-Process 'C:\Files\Install files\Firefox' -ArgumentList "/S" -Wait
if (Test-Path C:\Program Files(x86)\Firefox)
{
Write-Host "Firefox succesfully installed!"
}else
{
Write-Host "Error, FireFox hasn't been installed"
}
.... And continue like this for the other programs
I thought 'why not?' since all of the PC's are the same and I must admit that the code also works.
But how would someone would judge this method? And what are alternatives?
Thanks
Just a note, I think this is more a question for Server Fault or Super User.
There are some high end tools like Desired State Configuration that handle all this but it really depends on the environment. We build standalone servers in an offsite datacentre and it's so much more convenient and less setup to just have a script that installs and configures everything that can't be imaged. Sure it takes two hours to complete but it's unattended, and since the OS is installed from a template we guarantee they're all exactly the same.
Perhaps I wouldn't recommend this method if the hardware were drastically different or if not imaging the OS to run the script on, I used to have a batch file to deploy customer desktops (back in WinXP days) but eventually cut it down to just detect the model and install drivers because it was too much hassle to maintain. I would also not recommend this method if it will be maintained by more than one person, or if the person maintaining it will be maybe/definitely not long term staff.
Overall it's up to the company. DSC is certainly a more professional and complete approach and much more easily maintained by multiple people, so put it to them and if they won't devote resources for a deployment server and training then you don't have much choice either way.
I would recommend to have a look at something more professionell like "PowerShell App Deployment Toolkit". (http://psappdeploytoolkit.com/)
It will take some time for you to start, but it is not that complex.
It outputs logs and so on.
I have a list of Windows packages that I'm installing via powershell using the following command:
& mypatch.exe /passive /norestart
mypatch.exe is being passed from a list and it doesn't wait for the prior install to finish - it just keeps going. It builds up a huge window of installs that are pending installation. Also, I can't use $LASTEXITCODE to determine if the install succeeded or failed.
Is there anyway to make the installs wait before starting the next?
Start-Process <path to exe> -Wait
JesnG is correct in using start-process,
however as the question showed passing arguments, the line should be:
Start-Process "mypatch.exe" -argumentlist "/passive /norestart" -wait
The OP also mentioned determining if the install succeeded or failed. I find that using a "try, catch throw" to pick up on error states works well in this scenario
try {
Start-Process "mypatch.exe" -argumentlist "/passive /norestart" -wait
} catch {
# Catch will pick up any non zero error code returned
# You can do anything you like in this block to deal with the error, examples below:
# $_ returns the error details
# This will just write the error
Write-Host "mypatch.exe returned the following error $_"
# If you want to pass the error upwards as a system error and abort your powershell script or function
Throw "Aborted mypatch.exe returned $_"
}
Sure, write a one line batch script that runs the installer. The batch script will wait for the installer to finish before returning. Call the script from PowerShell which will in turn wait for the batch script to finish.
If you have access to how mypatch is written, you could have that create some random file when it completes that PowerShell can check for its existence in a while loop and just sleeps while the file doesn't exist.
If you don't, you could also have that batch script create a dummy file when the installer completes.
Yet another way, though probably the worst of all of these is to just hard-code a sleep timer (start-sleep) once you call the installer.
EDIT just saw JensG's answer. Didn't know about that one. Nice
I want my PowerShell script to stop when any of the commands I run fail (like set -e in bash). I'm using both Powershell commands (New-Object System.Net.WebClient) and programs (.\setup.exe).
$ErrorActionPreference = "Stop" will get you part of the way there (i.e. this works great for cmdlets).
However for EXEs you're going to need to check $LastExitCode yourself after every exe invocation and determine whether that failed or not. Unfortunately I don't think PowerShell can help here because on Windows, EXEs aren't terribly consistent on what constitutes a "success" or "failure" exit code. Most follow the UNIX standard of 0 indicating success but not all do. Check out the CheckLastExitCode function in this blog post. You might find it useful.
You should be able to accomplish this by using the statement $ErrorActionPreference = "Stop" at the beginning of your scripts.
The default setting of $ErrorActionPreference is Continue, which is why you are seeing your scripts keep going after errors occur.
Sadly, due to buggy cmdlets like New-RegKey and Clear-Disk, none of these answers are enough. I've currently settled on the following code in a file called ps_support.ps1:
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$PSDefaultParameterValues['*:ErrorAction']='Stop'
function ThrowOnNativeFailure {
if (-not $?)
{
throw 'Native Failure'
}
}
Then in any powershell file, after the CmdletBinding and Param for the file (if present), I have the following:
$ErrorActionPreference = "Stop"
. "$PSScriptRoot\ps_support.ps1"
The duplicated ErrorActionPreference = "Stop" line is intentional. If I've goofed and somehow gotten the path to ps_support.ps1 wrong, that needs to not silently fail!
I keep ps_support.ps1 in a common location for my repo/workspace, so the path to it for the dot-sourcing may change depending on where the current .ps1 file is.
Any native call gets this treatment:
native_call.exe
ThrowOnNativeFailure
Having that file to dot-source has helped me maintain my sanity while writing powershell scripts. :-)
A slight modification to the answer from #alastairtree:
function Invoke-Call {
param (
[scriptblock]$ScriptBlock,
[string]$ErrorAction = $ErrorActionPreference
)
& #ScriptBlock
if (($lastexitcode -ne 0) -and $ErrorAction -eq "Stop") {
exit $lastexitcode
}
}
Invoke-Call -ScriptBlock { dotnet build . } -ErrorAction Stop
The key differences here are:
it uses the Verb-Noun (mimicing Invoke-Command)
implies that it uses the call operator under the covers
mimics -ErrorAction behavior from built in cmdlets
exits with same exit code rather than throwing exception with new message
You need slightly different error handling for powershell functions and for calling exe's, and you need to be sure to tell the caller of your script that it has failed. Building on top of Exec from the library Psake, a script that has the structure below will stop on all errors, and is usable as a base template for most scripts.
Set-StrictMode -Version latest
$ErrorActionPreference = "Stop"
# Taken from psake https://github.com/psake/psake
<#
.SYNOPSIS
This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode
to see if an error occcured. If an error is detected then an exception is thrown.
This function allows you to run command-line programs without having to
explicitly check the $lastexitcode variable.
.EXAMPLE
exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed"
#>
function Exec
{
[CmdletBinding()]
param(
[Parameter(Position=0,Mandatory=1)][scriptblock]$cmd,
[Parameter(Position=1,Mandatory=0)][string]$errorMessage = ("Error executing command {0}" -f $cmd)
)
& $cmd
if ($lastexitcode -ne 0) {
throw ("Exec: " + $errorMessage)
}
}
Try {
# Put all your stuff inside here!
# powershell functions called as normal and try..catch reports errors
New-Object System.Net.WebClient
# call exe's and check their exit code using Exec
Exec { setup.exe }
} Catch {
# tell the caller it has all gone wrong
$host.SetShouldExit(-1)
throw
}
I'm new to powershell but this seems to be most effective:
doSomething -arg myArg
if (-not $?) {throw "Failed to doSomething"}
As far as I know, Powershell does not have any automatic handling of non-zero exit codes returned by sub-programs it invokes.
The only solution I know about so far to mimick the behavior of bash -e is to add this check after every call to an external command:
if(!$?) { Exit $LASTEXITCODE }
I came here looking for the same thing. $ErrorActionPreference="Stop" kills my shell immediately when I'd rather see the error message (pause) before it terminates. Falling back on my batch sensibilities:
IF %ERRORLEVEL% NEQ 0 pause & GOTO EOF
I found that this works pretty much the same for my particular ps1 script:
Import-PSSession $Session
If ($? -ne "True") {Pause; Exit}
Seems like simple rethrow does the trick.
param ([string] $Path, [string] $Find, [string] $Replace)
try {
((Get-Content -path $Path -Raw) -replace $Find, $Replace) | Set-Content -Path $Path
Write-Output Completed.
} catch {
# Without try/catch block errors don't interrupt program flow.
throw
}
Now output Completed appears only after successful execution.
for people coming here on 2021 this is my solution that covers both cmdlets and programs
function CheckLastExitCode {
param ([int[]]$SuccessCodes = #(0))
if (!$?) {
Write-Host "Last CMD failed" -ForegroundColor Red
#GoToWrapperDirectory in my code I go back to the original directory that launched the script
exit
}
if ($SuccessCodes -notcontains $LastExitCode) {
Write-Host "EXE RETURNED EXIT CODE $LastExitCode" -ForegroundColor Red
#GoToWrapperDirectory in my code I go back to the original directory that launched the script
exit
}
}
you can use it like this
cd NonExistingpath
CheckLastExitCode
Redirecting stderr to stdout seems to also do the trick without any other commands/scriptblock wrappers although I can't find an explanation why it works that way..
# test.ps1
$ErrorActionPreference = "Stop"
aws s3 ls s3://xxx
echo "==> pass"
aws s3 ls s3://xxx 2>&1
echo "shouldn't be here"
This will output the following as expected (the command aws s3 ... returns $LASTEXITCODE = 255)
PS> .\test.ps1
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
==> pass