I am not quite sure how to explain my problem, but I have a function that installs Office, imagine the person that runs this script does not have internet connection or does not have enough space on her hard drive. I have the XML file set to hide the setup interface so the user can't see the installation process. Just to be clear all my code works fine, just want add this feature so that if something goes wrong while the user runs the script I know where the error was.
This is my function:
Function Install-Office365OfficeProducts{
Write-Host ""
Start-Sleep -Seconds 5
Write-Host "Installing Office 365 ProPlus..."
# Installing Office 365 ProPlus
Install-Office365Product -path "$PSScriptRoot\setup.exe" -xmlPath "$PSScriptRoot\InstallO365.xml"
This is what I have tried:
if (Install-Office365OfficeProducts -eq 0) {
Write-Host "FAILED"}
I am very confused, I thought that a function that runs with no error returns 1 and when it runs with errors returns 0.
Also have tried to put the code like this:
try {
Install-Office365Product -path "$PSScriptRoot\setup.exe" -xmlPath "$PSScriptRoot\InstallO365.xml"
} catch {
Write-Host "Failed!"
}
EDIT:
Basically i want to be shown an error if the Office setup is not finished...
#Thomas
Function Install-Office365Product{
Param (
[string]$path,
[string]$xmlPath
)
$arguments = "/configure `"$xmlPath`""
try{
Start-Process -FilePath "$path" -ArgumentList "$arguments" -Wait -NoNewWindow -ErrorAction Stop
}catch{
Write-Host "It was not possible to install the product!"
}
}
Your try/catch-block inside Install-Office365OfficeProducts is useless, because Install-Office365Product will not throw anything, except you pass wrong arguments. The try/catch-block inside Install-Office365Product will most likely also not catch anything. But you can of course evaluate the return code of your installer called with Start-Process:
function Install-Office365Product {
Param (
[string]$path,
[string]$xmlPath
)
$arguments = "/configure `"$xmlPath`""
$process = Start-Process -FilePath "$path" -ArgumentList "$arguments" -Wait -PassThru -NoNewWindow
if ($process.ExitCode -eq 0) {
Write-Host "Installation successful"
} else {
Write-Host "Installation failed"
}
}
Instead of writing to stdout, you can of course also throw an exception and handle it later in a higher function.
Related
I am writing a powershell script that is running on Linux. The purpose of this script is to run the MS SQL Server and show its output, but when the user presses Ctrl+C or some error happens, the script takes a copy of the data folder and then after that exits.
[CmdletBinding()]
PARAM (
[Parameter(ValueFromPipelineByPropertyName)]
[string] $Command,
[Parameter(ValueFromPipelineByPropertyName)]
[string] $Args
)
BEGIN {
Write-Output "started $PSScriptRoot"
$currentErrorLevel = $ErrorActionPreference
# [console]::TreatControlCAsInput = $true
}
PROCESS {
try {
$ErrorActionPreference = 'SilentlyContinue';
$IsSqlRunning = Get-Process -name "sqlservr";
if ($null -eq $IsSqlRunning) {
start-process "/opt/mssql/bin/sqlservr" -wait -NoNewWindow
}
}
catch {
$ErrorActionPreference = $currentErrorLevel;
Write-Error $_.Exception
}
}
End {
$ErrorActionPreference = $currentErrorLevel;
#do backup
Create-Backup "/opt/mssql/bin/data"
Write-Output "finishd!"
}
I got a couple of problems with this script:
When I press Ctrl+C it breaks the main script and it never reaches to the Create-Backup section at the bottom of the script.
If I remove -Wait then the script won't show the sql log output
So my prefered solution is to run the sql with -Wait parameter and prevent the powershell to exit the code after I press Ctrl+C, but instead Ctrl+C close the sql instance
So I'm looking for a way to achieve both.
For simplicity I'll assume that your function only needs to support a single input object, so I'm using a simple function body without begin, process and end blocks, which is equivalent to having just an end block:
[CmdletBinding()]
PARAM (
[Parameter(ValueFromPipelineByPropertyName)]
[string] $Command,
[Parameter(ValueFromPipelineByPropertyName)]
[string] $Args
)
Write-Output "started $PSScriptRoot"
# No need to save the current value, because the modified
# value is local to the function and goes out of scope on
# exiting the function.
$ErrorActionPreference = 'SilentlyContinue';
try {
$IsSqlRunning = Get-Process -name "sqlservr";
if ($null -eq $IsSqlRunning) {
start-process "/opt/mssql/bin/sqlservr" -wait -NoNewWindow
}
}
catch {
Write-Error $_.Exception
}
finally {
# This block is *always* called - even if Ctrl-C was used.
Create-Backup "/opt/mssql/bin/data"
# CAVEAT: If Ctrl-C was used to terminate the run,
# you can no longer produce *pipeline* input at this point
# (it will be quietly ignored).
# However, you can still output to the *host*.
Write-Host "finished!"
}
If you really need to support multiple input objects, it gets more complicated.
I am trying to install software (executable) on several servers with various versions of PowerShell.
Normally below code works with no issue on PS4 and up. On PS3 it does not install anything, nor does it produce any errors on the remove server eventviewer. It treated as success by printing out "... -- installation succeeded" and exits. I've googled about and read that perhaps Start-Process is the culprit in PS3.
Begin {
$uncpath="\\remoteserveruncpath\" #"
$exe_parameter1 = "centralserver.com"
$creds = Get-Credential -Message "Password: " -Username "$($env:userdnsdomain)\$($env:username)"
}
Process {
$dnshostname = "server1","server2","server3"
ForEach ($server in $dnshostname) {
Invoke-Command -ComputerName $server -ScriptBlock {
param($server_int,$exe_parameter1_int,$uncpath_int,$creds_int)
(New-Object -ComObject WScript.Network).MapNetworkDrive('Z:',"$($uncpath_int)", $false, "$($creds_int.Username)", "$($creds_int.GetNetworkCredential().Password)")
$arguments = "/param_1=$exe_parameter1_int /param_2=$($server_int.ToLower()) /start-program=1 /S"
If((Start-Process "Z:\installer.exe" -ArgumentList $arguments -Wait -Verb RunAs).ExitCode -ne 0) {
Write-Host "$server_int -- installation succeeded"
} else {
Write-Error "$server_int -- installation failed"
}
} -ArgumentList $server,$exe_parameter1,$uncpath,$creds;
}
}
Any advice? Many thanks!
Without -PassThru, Start-Process produces no output, so accessing .ExitCode effectively returns $null, always.
And since $null -ne 0 is always $true, your code always indicates success.
In order to get the installer command's true exit code, you therefore need to use the following (note the addition of -PassThru):
if ((Start-Process -PassThru 'Z:\installer.exe' -ArgumentList $arguments -Wait -Verb RunAs).ExitCode -ne 0) { ... }
I am trying to write something that will prompt for a PC name and then remotely perform a folder permission change. Everything is chaotic looking but I am struggling with changing the execution policy after the Enter-PSSession - it seems it just gets stuck and won't proceed further to my function.
This is the rough draft of what I have thus far. Any help is appreciated.
$PromptedPC = Read-Host -Prompt "`n `n Enter PC Number"
Enter-PSSession -ComputerName $PromptedPC
Write-Host `n"Do you want to change folder permissions?" -ForegroundColor Green
$ReadAnswer = Read-Host " ( y / n ) "
PowerShell.exe -noprofile -ExecutionPolicy Bypass
switch ($ReadAnswer)
{
Y {
function Grant-userFullRights {
[cmdletbinding()]
param(
[Parameter(Mandatory=$true)]
[string[]]$Files,
[Parameter(Mandatory=$true)]
[string]$UserName
)
$rule=new-object System.Security.AccessControl.FileSystemAccessRule ($UserName,"FullControl","Allow")
foreach($File in $Files) {
if(Test-Path $File) {
try {
$acl = Get-ACL -Path $File -ErrorAction stop
$acl.SetAccessRule($rule)
Set-ACL -Path $File -ACLObject $acl -ErrorAction stop
Write-Host "Successfully set permissions on $File"
} catch {
Write-Warning "$File : Failed to set perms. Details : $_"
Continue
}
} else {
Write-Warning "$File : No such file found"
Continue
}
}
}
}
}
Grant-userFullRights -Files 'C:\ProgramData\New World Systems\' -UserName "BUILTIN\Users"
I did get the function information from: http://techibee.com/powershell/grant-fullcontrol-permission-to-usergroup-on-filefolder-using-powershell/2158
I am just trying to put something together that will work for a programdata folder our users need full control of that keeps reverting permissions to something lower than full access.
You start a new PowerShell with the ExecutionPolicy Bypass.
Sine you are already in PowerShell, the proper way to configure the Execution Polcy is Set-ExecutionPolicy.
However, since you don't invoke any script file, there is no need to change the Execution Policy.
How do you exit from an invoke-command script block running on a remote server? I have tried next.
Here is my code:
$Res = Invoke-Command -Session $Ses -ArgumentList ($ROOTdir, $PARAMS.SYS, $PARAMS.main, $PARAMS.zip) -ScriptBlock {
Param($ROOTdir, $SYS, $MAIN, $ZIP)
$list | %{
$completed = $false
$retrycount = 1
while (-not $completed) {
try {
$Copytime = (Measure-Command {
Copy-Item -Path $_.FullName -Destination ($SYS.KITCHENdir) -Force -ErrorVariable copyerror
}).TotalSeconds
$completed = $true
} catch {
if ($retrycount -gt $MAIN.Retry ) {
break or exit #HOW STOP EXECUTING NEXT STEPS AND EXIT
} else {
Start-Sleep $MAIN.DelayRetry
$retrycount++
}
}
}
#IF copy bad result need stop and exit
#The final one can produce flavors here, but it does not look quite kosher
#next steps
}
I suspect you want to use the Exit keyword.
"The exit keyword is used to exit from contexts; it will exit the
(currently running) context where your code is running. This means
that if you use this keyword in a script, and launch the script
directly from your console, it will exit both the script and the
console since they're both running in the same context. However, if
you use the exit keyword in a function within the script, and call
that function from the same script, it will just exit the script and
not the entire console. The exit keyword is best used in functions,
and when those functions are called in a script. It's a great way to
terminate execution of functions."
https://www.pluralsight.com/blog/it-ops/powershell-terminating-code-execution
I have a while($true) loop with a start-sleep -s 60 at the end of it. The purpose is to start an external PowerShell script as a different user which will run through a list of servers, check the event log for changes within the last minute and react accordingly.
Since my while loop (below) is using the -credential flag to run the script as someone else, I'm concerned about errors (e.g. account locked out, expired password, missing file, etc).
I tried an if ($error) statement, and changed the filename of the external script, but I was never alerted. I'm thinking it's because it never stops to re-check itself?
while($true) {
# Start the scan
Start-Process powershell -Credential $credentials -ArgumentList '-noprofile -command & c:\batch\02-Scan.ps1'
# Sleep 60 seconds
start-sleep -s 60
}
I suppose I could change my scheduled task to run every minute, but so far this loop seems to have been working great. Just want to introduce error checking while the loop is active.
Have you tried try/catch blocks? Wrong credentials is a terminating error, so the rest of the code in the try block won't run after a credentials-error. When you catch it, you can do whatever you want.. Ex.
try {
Start-Process powershell -Credential $credentials -ArgumentList '-noprofile -command & c:\batch\02-Scan.ps1'
} catch {
#Catches terminating exceptions and display it's message
Write-Error $_.message
}
If you want to catch all errors, add -ErrorAction Stopto the Start-Processline. As said, credentials should be a terminating error, which make the erroraction parameter unnecessary
EDIT Why would you use Start-Process to run a script in the first place? I switched it to Invoke-Command which runs a powershell script remotely. When the scriptfile is missing, you will recieve a NON-terminating error. Because it's a non-terminating error, we need to use the -ErrorAction Stop parameter. To catch the missing-file error and every other error(like credentials), use something like this:
try { Invoke-Command -ScriptBlock { & c:\batch\02-Scan.ps1 } -ErrorAction Stop
} catch {
if ($_.Exception.GetType().Name -eq "CommandNotFoundException") {
Write-Error "File is missing"
} else {
Write-Error "Something went wrong. Errormessage: $_"
#or throw it directly:
#throw $_
}
}
Maybe?
while($true) {
# Start the scan
try{
Start-Process powershell -Credential $credentials -ArgumentList '-noprofile -command & c:\batch\02-Scan.ps1' -ErrorAction Stop
}
catch {
send-alert
break
}
# Sleep 60 seconds
start-sleep -s 60
}