Robocopy script /w Powershell + Mail report for failure - powershell

I'm studying for Network\System Admin certification (MCSE is in it) and among other things we started to delve into powershell scripting.
At any rate my instructor gave me a challenge to do as follows:
I need to create a PS script which will run Robocopy tool in order to mirror a folder from point A to Point B and that script should run as a schedule task (/w Task Scheduler).
If robocopy have encountered any errors the script should send a mail via the Company's exchange mail server to the Admin in charge of the process.
I'm not Scripting savvy of any sort so I had to sit and learn the syntax, command etc before I could start working.
Here is what I got so far:
# PowerShell Robocopy Script V.1 | Liron Ben-David
# Variables
$Source ="C:\Source\"
$Destination = "C:\Destination\"
$ExitCode = "Null"
#Copying Process
Robocopy $Source $destination *.* /mir /r:100 /w:10
# Return Code Section
if ($LASTEXITCODE -eq 0) {$ExitCode = "Succeeded, Code: $LASTEXITCODE" }
elseif (($LASTEXITCODE -gt 1) -and ($LASTEXITCODE -lt 17)) {$ExitCode = "Failure, Code: $LASTEXITCODE" }
else {$ExitCode = "No Return Code"}
Write-Host $ExitCode
## Failure Report
if ($ExitCode -gt 1) {Send-MailMessage -to "Admin (Admin#Domain.com)" -From "Machine-Robocopy1 (M1RC#domain.com)" -Subject $ExitCode -SmtpServer mail.domain.com}
else {exit}
basically if there is any kind of error in the process the script supposed to send an email with the script error code as the "Subject" of the message.
First of all:
Does this script contain any embarassing mistakes I did and which I should avoid in the future?
Secondarily:
I never used the CLI to send email messages, using the Send-Mail CMDLET I should have used a credentials switch but according to what I've read\watched it seems it will pop up a window for input.
Considering I'm setting this script for a Domain Environment wouldn't the SSO feature will kick in when I try to send the message?
EDIT:
Also noticed I mixed the error codes so I need to re-calibrate the error code lines.
EDIT 2: Version 2 bsaed on your help,
# PowerShell Robocopy Script V.2 | Liron Ben-David
# Variables
$Source = "C:\Source\"
$Destination = "C:\Destination\"
$ExitCode = "Null"
$ReturnCodeValue = "Void"
$Log = "C:\RoboLogs\RobocopyLog.txt"
#Copying Process
Robocopy $Source $destination *.* /mir /r:100 /w:10 /UniLog:C:\RoboLogs\RobocopyLog.txt /v
# Return Code Section
if (($LASTEXITCODE -eq 0) -or ($LASTEXITCODE -eq 1) -or ($LASTEXITCODE -eq 3) -or ($LASTEXITCODE -eq 5) -or ($LASTEXITCODE -eq 6) -or ($LASTEXITCODE -eq 7)) {$ExitCode = "Succeeded, Code: $LASTEXITCODE" }
elseif (($LASTEXITCODE -eq 2) -or ($LASTEXITCODE -eq 4)) {$ExitCode = "Error, Code: $LASTEXITCODE" }
elseif ($LASTEXITCODE -gt 7) {$ExitCode = "Failure, Code: $LASTEXITCODE" }
else {$ExitCode = "No Return Code"}
$ReturnCodeValue = $LASTEXITCODE
# Debug Section
#Write-Host "Did the script completed succesfuly:" $?
#Write-Host "Script Exit Code:" $ExitCode
#Start-Sleep -s 4
## Failure Report
if (($ReturnCodeValue -gt 7) -or ($ReturnCodeValue -eq 2) -or ($ReturnCodeValue -eq 4)) {Send-MailMessage -to "Admin (Admin#Domain.com)" -From "Machine-Robocopy1 (M1RC#domain.com)" -Subject $ExitCode -SmtpServer mail.domain.com –Attachments $Log -UseSsl}
else {exit}

Related

Jenkins Job is failing even PS script is successfully executed

I have below script which will
Stop WebService
Delete WebService
Copy items from Jenkins workspace to server path
Create WebService
Start WebService
I run this PS script in Jenkins job and after execution of this,I get a email as Build is failed.
val STOPPED
[SC] DeleteService SUCCESS
val DELETED
Remove-Item : Cannot remove item \\Location.dll: Access to the
path '\\Location.dll' is denied.
At C:\Users\Administrator\AppData\Local\Temp\hudson7587011077362516847.ps1:33 char:5
+ Remove-Item "$val\*" -Force -Recurse
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (\\10.0.1.190\d$...h\Grpc.Core.dll:FileInfo) [Remove-Item], Unauthoriz
edAccessException
+ FullyQualifiedErrorId : RemoveFileSystemItemUnAuthorizedAccess,Microsoft.PowerShell.Commands.RemoveItemCommand
My PS script
# force strict - so any variable used before being assigned causes an error
Set-PsDebug -Strict
# force PowerShell to exit with a non-zero code on teh first error
$ErrorActionPreference = 'Stop'
# set $LASTEXITCODE to zero, so the script doesn't fail if no exit code returned
$LASTEXITCODE = 0
# set directories here once, so we can reuse
$MystiflySearchDir = "\\Location"
$ReleaseDir = "C:\Location"
# get directory contents (are you expecting these to return to Jenkins?)
Get-ChildItem "$ReleaseDir\*"
# create the search directory if it doesn't exist
if (-not (Test-Path -Path $val -PathType Container)) { New-Item -Path $val-type directory -Force }
# get the service, but fail gracefully if it doesn't exist
$service = Get-Service -Name val -Computername $env:SERVER -ErrorAction SilentlyContinue
# if we have a service, stop and delete it
if($service.Status)
{
sc.exe \\$env:SERVER stop val
if ($LASTEXITCODE -ne 0) { throw "error stopping the service: $LASTEXITCODE" }
Write-Host "val STOPPED"
Start-Sleep -s 10
sc.exe \\$env:SERVER delete val
if ($LASTEXITCODE -ne 0) { throw "error deleting the service: $LASTEXITCODE" }
Write-Host "val DELETED"
Start-Sleep -s 25
Remove-Item "$val\*" -Force -Recurse
}
# copy release to search
xcopy "$ReleaseDir\*" $val/k/e/d/Y
# (re)create the service
sc.exe \\$env:SERVER create val start=auto DisplayName= "value" binPath= D:\Location.exe
if ($LASTEXITCODE -ne 0) { throw "error creating the service: $LASTEXITCODE" }
sc.exe \\$env:SERVER description val "val"
if ($LASTEXITCODE -ne 0) { throw "error adding description to service: $LASTEXITCODE" }
sc.exe \\$env:SERVER start val
if ($LASTEXITCODE -ne 0) { throw "error starting the service: $LASTEXITCODE" }
Write-Host "val STARTED"
Is there anything i am missing here
Your code needed a little tidying up, as you had a couple of repeating sections, that were performed whether the service existed or not. Also flipped the if logic.
Added some steps at the start of your code to cause PowerShell to be more strict and drop out on any errors with a non-zero code.
This has not been tested, so may need a little fine-tuning. However, it should be 95% there, hopefully!
# force strict - so any variable used before being assigned causes an error
Set-PsDebug -Strict
# force PowerShell to exit with a non-zero code on the first error
$ErrorActionPreference = 'Stop'
# set $LASTEXITCODE to zero, so the script doesn't fail if no exit code returned
$LASTEXITCODE = 0
# se directories her once, so we can reuse
$AirSearchDir = "\\Location"
$ReleaseDir = "C:\Location"
# get directory contents (are you expecting these to return to Jenkins?)
Get-ChildItem "$ReleaseDir\*"
# create the search directory if it doesn't exist
if (-not (Test-Path -Path $AirSearchDir -PathType Container)) { New-Item -Path $AirSearchDir -type directory -Force }
# get the service, but fail gracefully if it doesn't exist
$service = Get-Service -Name val -Computername $env:SERVER -ErrorAction SilentlyContinue
# if we have a service, stop and delete it
if($service.Status)
{
sc.exe \\$env:SERVER stop val
if ($LASTEXITCODE -ne 0) { throw "error stopping the service: $LASTEXITCODE" }
Write-Host "val STOPPED"
sc.exe \\$env:SERVER delete val
if ($LASTEXITCODE -ne 0) { throw "error deleting the service: $LASTEXITCODE" }
Write-Host "val DELETED"
}
# copy release to search
Copy-Item "$ReleaseDir\*" $AirSearchDir -Force -Recurse
# (re)create the service
sc.exe \\$env:SERVER create val start=auto DisplayName="val" binPath= D:\Location.exe
if ($LASTEXITCODE -ne 0) { throw "error creating the service: $LASTEXITCODE" }
sc.exe \\$env:SERVER description val "val"
if ($LASTEXITCODE -ne 0) { throw "error adding description to service: $LASTEXITCODE" }
sc.exe \\$env:SERVER start val
if ($LASTEXITCODE -ne 0) { throw "error starting the service: $LASTEXITCODE" }
Write-Host "val STARTED"
Try running your Jenkins with the administrator account, the reason why you are getting this error is because your jenkins user is unable to interact with the services.
Running it with the administrator account will give it sufficient privileges to interact with other services.

Script for Azure Backup notifications

I's just a basic one, I am new to Powershell. Trying to get the statement below working.
$date = (Get-Date).AddDays(-1)
$currentdate = Get-Date -Format d
$check = Get-WinEvent -FilterHashtable #{LogName="CloudBackup";StartTime=$date;ID=3} *>$null
if ($check -eq $true) {
Write-Host "`nOK: Azure Backup was successful on $currentdate"
exit 0
} else {
Write-Host "`nCritical: Problem with Azure Backup - $currentdate"
exit 2
}
Specially if ($check -eq $true) doesn't seem to do what expected. As $check is checking for event ID 3 in the eventlog, if it's there it should return true, if not false. Unfortunately it's returning only false every time.
Could someone please advise? Is there a better way to do that?
$check = Get-WinEvent ... *>$null
Your redirection is suppressing all output, so $check always has the value $null, which is interpreted as $false in a boolean operation.
What you want to use is the automatic variable $? to check it if the last PowerShell operation was successful.
if ($?) {
Write-Host "OK: Azure Backup was successful on $currentdate"
exit 0
} else {
Write-Host "Critical: Problem with Azure Backup - $currentdate"
exit 2
}

How to ensure IIS website is completely stopped in Powershell?

I've got a Powershell script that stops an IIS website and corresponding app pool and then deletes the app logs (log4net logs). Here is the script snippet:
stop-website -name "MyWebsite"
stop-webapppool -name "MyWebsite"
del c:\inetpub\MyWebsite\logs\*.*
The problem is stop-website and stop-webapppool seem to return before the website is completely shutdown which results in the delete failing saying the file is being used by another process:
del : Cannot remove item C:\inetpub\MyWebsite\logs\App.log: The process cannot access the file 'C:\inetpub\MyWebsite\logs\App.log' because it is being used by another process.
If I add a 10 second sleep between the stop commands and the del command then the logs are deleted successfully. This is very hackish though and not reliable. Is there a way to force the stop-website/stop-webapppool commands to not return until the website/apppool is completely stopped?
Thanks.
Implemented solution from the below link. I will wait ~60 seconds and then kill the IIS process if it hasn't stopped.
https://greenfinch.ie/blog/powershellscript.html
"Stopping IIS site [$name]" >> $logFile
stop-website -name $name
"Stopping app pool [$name]" >> $logFile
stop-webapppool -name $name
$sleepTime = 5
$processId = $TRUE
while ($processId)
{
$processId = Get-WmiObject -Class win32_process -filter "name='w3wp.exe'" |
?{ ($_.CommandLine).Split("`"")[1] -eq $name } |
%{ $_.ProcessId }
if ($sleepTime -gt 60)
{
"Waited [$sleepTime] sec for process [$processId] to stop and it is still running. Killing it." >> $logFile
Stop-Process $processId
break
}
if ($processId)
{
"App pool [$name] is running with process ID: [$processId]. Sleeping for [$sleepTime] sec and then checking again." >> $logFile
Start-Sleep -s $sleepTime
$sleepTime = $sleepTime + 10
}
}
You can use these two commands to check the status of the website/app, say after 10 seconds, then use an If statement to delete logs only when the status returned is stopped
Get-WebsiteState -name "MyWebsite"
Get-WebAppPoolState -name "MyWebsite"
This loop should help you too
$currentRetry = 0;
$success = $false;
do{
$status = Get-WebAppPoolState -name "MyWebsite"
if ($status -eq "Stopped"){
<....your code here....>
$success = $true;
}
Start-Sleep -s 10
$currentRetry = $currentRetry + 1;
}
while (!$success -and $currentRetry -le 4)
Updated Apr 24, 2019
Based on comment and current cmdlet document, it appears the return type is indeed an object. Thus presumably can be handled as commented or the line snippet below. Author no longer have access to Windows Server environment therefore did not directly modify original answer nor able to test the update
if ($status.Value -eq "Stopped")
After you run 'Stop-WebAppPool' the state of the WebAppPool will be "Stopping" and it may take a few seconds before the state of the WebAppPool is actually "Stopped".
Here is a little function to help with the WebAppPoolState
function Stop-AppPool ($webAppPoolName,[int]$secs) {
$retvalue = $false
$wsec = (get-date).AddSeconds($secs)
Stop-WebAppPool -Name $webAppPoolName
Write-Output "$(Get-Date) waiting up to $secs seconds for the WebAppPool '$webAppPoolName' to stop"
$poolNotStopped = $true
while (((get-date) -lt $wsec) -and $poolNotStopped) {
$pstate = Get-WebAppPoolState -Name $webAppPoolName
if ($pstate.Value -eq "Stopped") {
Write-Output "$(Get-Date): WebAppPool '$webAppPoolName' is stopped"
$poolNotStopped = $false
$retvalue = $true
}
}
return $retvalue
}
you can run this function using e.g.
Stop-AppPool "MyWebsite" 30
and check the return-value to see if the WebAppPool has stopped within the given seconds
The simplest way to stop the app pool and get it into Stopped state is to use appcmd.exe. It will return when the app pool is really stopped or you'll get an error
Just do this on PowerShell:
& $env:windir\system32\inetsrv\appcmd.exe stop apppool /apppool.name:"YourAppPoolName"
When your AppPool is correctly stooped you'll get this message:
"YourAppPoolName" successfully stopped
I fix the #user4531 code It would be failed if the app pool is stopped before :
function Stop-AppPool ($webAppPoolName,[int]$secs) {
$retvalue = $false
$wsec = (get-date).AddSeconds($secs)
$pstate = Get-WebAppPoolState -Name $webAppPoolName
if($pstate.Value -eq "Stopped") {
Write-Output "WebAppPool '$webAppPoolName' is stopped already"
return $true
}
Stop-WebAppPool -Name $webAppPoolName
Write-Output "$(Get-Date) waiting up to $secs seconds for the WebAppPool '$webAppPoolName' to stop"
$poolNotStopped = $true
while (((get-date) -lt $wsec) -and $poolNotStopped) {
$pstate = Get-WebAppPoolState -Name $webAppPoolName
if ($pstate.Value -eq "Stopped") {
Write-Output "WebAppPool '$webAppPoolName' is stopped"
$poolNotStopped = $false
$retvalue = $true
}
}
return $retvalue
}
It can use like this :
Stop-AppPool "SSO" 30
Here is how I did it with Get-IISServerManager.
$manager = Get-IISServerManager
$site = $manager.Sites["mySiteName"]
if($site.State -ne "Stopped") {
$site.Stop()
}
while ($site.State -ne "Stopped") {
"waiting 1 second for site to stop..."
Start-Sleep -s 1
}
"site stopped"

Get an progress output for Export-VM

I recently created a script that's able to create a chosen number of Hyper-V VMs from a "template", by exporting one VM and import it with different ids and names.
Everything is working smoothly, but the export takes quite some time. Is there any way to get an progress output from the export-vm? If I look in the hyper-v command center, I see that my VM is exported after i started the script, and it has a progress value, too, so somehow there seems to be a way to get the current progress...
It would also be nice to have a progress output for Import-VM, too, but that's not really important.
If you are interested, here is my current script. I know that the hard-coded paths and other ascpects aren't really nice, so please don't tell me anything about code style or something like this. First comes a working script, then comes nice code.
param(
[int]$Anzahl = 0,
[string]$BasisVM = 'Schulung',
[string]$ExportDir = 'C:\VMConf\Export\',
[string]$ExportConf = 'Schulung\Virtual Machines\0D444AF2-3E63-4ACF-867E-34440AA99C42.xml',
[string]$VMDir = 'C:\VMs\',
[string]$VMNamePrefix = 'Schulung'
)
if ($Anzahl -eq 0) {
$Anzahl = Read-Host "Bitte Anzahl der benötigten VMs eingeben"
}
"`nStarte Export der Vorlage..."
if (-Not(Test-Path $ExportDir)) {
New-Item -ItemType directory -Path $ExportDir | out-null
}
Export-VM -Name $BasisVM -Path $ExportDir
"Abgeschlossen.`n"
if (Test-Path $VMDir) {
$err = $null
$i = 1
while (-not($err)) {
if ($i -gt 1) {
"Alte VM Nummer $($i - 1) wurde gelöscht."
}
Remove-VM -Name $($VMNamePrefix + ("{0:D2}" -f $i)) -Force -ErrorVariable err -ErrorAction SilentlyContinue
$i++
}
Remove-Item -r $VMDir | out-null
New-Item -ItemType directory -Path $VMDir | out-null
}
"`n$Anzahl VMs werden erstellt..."
for ($i=1; $i -le $Anzahl; $i++) {
"`tErstelle VM Nummer $i..."
$name = ($VMNamePrefix + ("{0:D2}" -f $i))
$path = ($VMDir + $name)
$VM = Import-VM -Path $($ExportDir + $ExportConf) -VhdDestinationPath $($path + "/Virtual Hard Discs") -VirtualMachinePath $path -Copy -GenerateNewId
Rename-VM -VM $VM -NewName $name
"`t$VM Nummer $i wurde erzeugt."
}
"Abgeschlossen.`n"
"Aufräumen..."
if (Test-Path $ExportDir) {
Remove-Item -r $ExportDir | out-null
}
"Abgeschlossen."
You could start the export (and even imports) as a background job and then log the progress of the job. Sample code:
$ExportJob = Export-VM -Name $BasisVM -Path $ExportDir -AsJob;
while( $ExportJob.State -eq "Running" -or $ExportJob.State -eq "NotStarted")
{
Write-Output ("[Export] " + $($ExportJob.Progress.PercentComplete) + "% complete");
sleep(5);
}
if($ExportJob.State -ne "Completed")
{
Write-Error ("Export Job did not complete: " +$ExportJob.State);
throw $ExportJob.Error;
}

Powershell Script triggered by Task Scheduler not executing Conditional Logic

I have a basic PowerShell (v4) script running on Server 2012. I decided to have Task Scheduler task it. Task scheduler instance is running as domain and machine administrator, Runs whether the user is logged on or not and runs with Highest Privileges. It's configured for Windows Server 2012 R2.
It starts a program of "Powershell" and this is my argument: "-ExecutionPolicy Bypass -file F:\AdMgmt\scripts\newcheckandcopy.ps1"
The problem: It executes some of the script just fine - however, I have an "If/Else" statement that it ignores. I've tried this with two scripts now and the Task scheduler always executes fine, but doesn't handle the If/Else stuff - it just skips over it and runs everything else.
This is the text of the script (it runs perfectly from the powershell console when logged in as the same account that runs the task):
$path = 'Q:\'
$stats = 0
$msg = ''
$days = 1
$hours = 0
$mins = 0
$logtime = Get-Date -uFormat "%y%m%d%H%M"
$files = #(Get-ChildItem -Recurse -Path $path -Include '*.*' | ?{ $_.LastWriteTime -lt (Get-Date).AddDays(-$days).AddHours(-$hours).AddMinutes(-$minutes) -and $_.psIsContainer -eq $false})
if ($files -ne $null) {
$f_names = [System.String]::Join('|',$files)
$msg = 'Message: ' + $f_names
$stats = $files.Count
Send-MailMessage -to "domain#test.com" -from "domain#test.com" -subject "FileMaker Files older than 1 Day" -body $msg -smtpserver "smtp-relay.test.com"
} else {
$msg = 'Message: 0 files exceed defined age'
Send-MailMessage -to "domain#test.com" -from "domain#test.com" -subject "FileMaker Files OK" -body $msg -smtpserver "smtp-relay.test.com"
}
Copy-Item Q:\* F:\admgmt\ -recurse
Add-Content f:\admgmt\logs\checkandcopy.txt $logtime
Write-Host $msg
Write-Host "Statistic: $stats"
I'm guessing your issue is you are running the script outside of user context. the Q:\ drive would not be available to it. PowerShell supports UNC paths so you might be able to substitute the actual path that Q:\ points to.