Script for Azure Backup notifications - powershell

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
}

Related

Powershell Error: Exception Calling "Approve" with "2" argument(s): Value cannot be null

I'm writing a powershell script to automate WSUS. One of the functions approves non-superseded updates to a sandbox testing computer group in order to download/install them on the console. However, all updates it finds return this same error. Here is the code for my definitions and the approval function:
[String]$updateServer1 = hostname
[Boolean]$useSecureConnection = $False
[Int32]$portNumber = 8530
[void][reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")
$updateServer = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($updateServer1,$useSecureConnection,$portNumber)
$updatescope = New-Object Microsoft.UpdateServices.Administration.UpdateScope
$u = $updateServer.GetUpdates($updatescope)
$install = [Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Install
$group = $updateServer.GetComputerTargetGroups | where-object {$_.Name -eq "Update Testing"}
function Approve-Nonsuperseded {
Write-host "Creating new Computer Group to approve updates for installation..." -foregroundcolor green
try {
$updateserver.CreateComputerTargetGroup("Update Testing")
}
catch {
Write-host "Update Group already exists. Moving on..." -ForegroundColor Green
}
$count = 0
Write-host "Approving new updates for installation..." -foregroundcolor green
foreach ($u2 in $u )
{
if ($u2.IsDeclined -ne 'True' -and $u2.IsSuperseded -ne 'True' -and $u2.CreationDate -ge $PatchDay)
{
write-host Approving Update : $u2.Title
$u2.Approve($install,$group)
$count = $count + 1
}
}
write-host Total Approved Updates: $count
}
It returns all the correct updates that are meant to be approved, but always gives me that same error on the $u2.Approve($install,$group) line. I'd appreciate any insight. Thanks!
$u2 should be a string and not an variable. Try using $u2.Approve("Install",$group)
The [Microsoft.UpdateServices.Administration.UpdateApprovalAction] namespace only provides the options ;)

Extract Event Time from Windows Log using PS

I am executing this powershell script to check the Windows Event Log viewer for a speficic job result.
It's working, however I'm stuck on one part...
I'm trying to do is pull the last/most recent event time/date and add it to the exit code.
"Task Completed Successfully at **_______**"
I have looked around a bit and tried a few different approaches without success... not the best at PS, can someone assist?
Param(
[string]$Task,
[string]$PastMinutes
)
$StartAt = (Get-Date).AddMinutes(-$PastMinutes)
$ErrorActionPreference = "SilentlyContinue"
$action = (Get-WinEvent -FilterHashtable #{logname="Microsoft-Windows-TaskScheduler/Operational"; id=102; StartTime=$StartAt} |
Where-Object {($_.Message -like $Task) -and ($_.Message -like "Task Scheduler Successfully Finished*")})
if ($action.count -ne "0") {
Write-Host "OK: "$Task" Task Completed Successfully at _______!"
Exit 0
} else {
Write-Host "CRITICAL: "$Task" Task Failed to Complete!"
Exit 2
}
I really do hate condensing code in ultralong lines until it is nearly unreadable.
As $action(s) has a .TimeCreated property, I'd use that.
This may work:
Param(
[string]$Task,
[string]$PastMinutes
)
$StartAt = (Get-Date).AddMinutes(-$PastMinutes)
$ErrorActionPreference = "SilentlyContinue"
$FilterHashTable = #{
logname = "Microsoft-Windows-TaskScheduler/Operational"
id = 102
StartTime = $StartAt
}
$actions = (Get-WinEvent -FilterHashtable $FilterHashTable |
Where-Object {($_.Message -like $Task) -and
($_.Message -like "Task Scheduler Successfully Finished*")})
## set negative result hopefully overwritten by action
$Result = "CRITICAL: {0} Task Failed to Complete!" -F $Task
if ($actions){
ForEach($action in $actions){
$Result = "OK: {0} Task Completed Successfully at {1}" -F $Task,$action.TimeCreated
}
}

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"

Powershell and System Center "You cannot call a method on a null-valued expression

I'm trying to create a script that runs on my clients computers and checks for a pending reboot. If there is a pending reboot it pops up a message that asks the user to reboot or defer. This works great when I try it on my local computer. However, once I put it into System Center and deploy it, it does not run and I get the error "Warning: You cannot call a method on a null-valued expression" Any ideas? Here is the script
[CmdletBinding()]
param(
[Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[Alias("CN","Computer")]
[String[]]$ComputerName="$env:COMPUTERNAME",
[String]$ErrorLog
)
Begin
{
# Adjusting ErrorActionPreference to stop on all errors, since using [Microsoft.Win32.RegistryKey]
# does not have a native ErrorAction Parameter, this may need to be changed if used within another
# function.
$TempErrAct = $ErrorActionPreference
$ErrorActionPreference = "Stop"
}#End Begin Script Block
Process
{
Foreach ($Computer in $ComputerName)
{
Try
{
# Setting pending values to false to cut down on the number of else statements
$PendFileRename,$Pending,$SCCM = $false,$false,$false
# Setting CBSRebootPend to null since not all versions of Windows has this value
$CBSRebootPend = $NULL
# Querying WMI for build version
$WMI_OS = Get-WmiObject -Class Win32_OperatingSystem -Property BuildNumber, CSName -ComputerName $Computer
# Making registry connection to the local/remote computer
$RegCon = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]"LocalMachine",$Computer)
# If Vista/2008 & Above query the CBS Reg Key
If ($WMI_OS.BuildNumber -ge 6001)
{
$RegSubKeysCBS = $RegCon.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\").GetSubKeyNames()
$CBSRebootPend = $RegSubKeysCBS -contains "RebootPending"
}#End If ($WMI_OS.BuildNumber -ge 6001)
# Query WUAU from the registry
$RegWUAU = $RegCon.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\")
$RegWUAURebootReq = $RegWUAU.GetSubKeyNames()
$WUAURebootReq = $RegWUAURebootReq -contains "RebootRequired"
# Closing registry connection
$RegCon.Close()
# Determine SCCM 2012 Client Reboot Pending Status
# To avoid nested 'if' statements and unneeded WMI calls to determine if the CCM_ClientUtilities class exist, setting EA = 0
$CCMClientSDK = $null
$CCMSplat = #{
NameSpace='ROOT\ccm\ClientSDK'
Class='CCM_ClientUtilities'
Name='DetermineIfRebootPending'
ComputerName=$Computer
ErrorAction='SilentlyContinue'
}
$CCMClientSDK = Invoke-WmiMethod #CCMSplat
If ($CCMClientSDK)
{
If ($CCMClientSDK.ReturnValue -ne 0)
{
Write-Warning "Error: DetermineIfRebootPending returned error code $($CCMClientSDK.ReturnValue)"
}#End If ($CCMClientSDK -and $CCMClientSDK.ReturnValue -ne 0)
If ($CCMClientSDK.IsHardRebootPending -or $CCMClientSDK.RebootPending)
{
$SCCM = $true
}#End If ($CCMClientSDK.IsHardRebootPending -or $CCMClientSDK.RebootPending)
}#End If ($CCMClientSDK)
Else
{
$SCCM = $null
}
# If any of the variables are true, set $Pending variable to $true
If ($CBSRebootPend -or $WUAURebootReq -or $SCCM)
{
$Pending = $true
}#End If ($CBS -or $WUAU)
If ($Pending = $CBSRebootPend -or $WUAURebootReq -or $SCCM)
{
$a = new-object -comobject wscript.shell
$b = $a.popup(“A Reboot is Pending, Press ""OK"" to reboot now or ""Cancel"" to reboot later.“,240,”CRISTA IT”,1)
if($b -eq 1) {
Restart-Computer
}
if($b -eq 2) {
#cancle was selected in the box. So should exit
exit(1)
}
}
}#End Try
Catch
{
Write-Warning "$Computer`: $_"
# If $ErrorLog, log the file to a user specified location/path
If ($ErrorLog)
{
Out-File -InputObject "$Computer`,$_" -FilePath $ErrorLog -Append
}#End If ($ErrorLog)
}#End Catch
}#End Foreach ($Computer in $ComputerName)
}#End Process
End
{
# Resetting ErrorActionPref
$ErrorActionPreference = $TempErrAct
}#End End
PLEASE NOTE: I did not create this script fully but used different free scripts online to create this.

PowerShell console timer

I am attempting to create a simple console timer display.
...
$rpt = $null
write-status "Opening report", $rpt
# long-running
$rpt = rpt-open -path "C:\Documents and Settings\foobar\Desktop\Calendar.rpt"
...
function write-status ($msg, $obj) {
write-host "$msg" -nonewline
do {
sleep -seconds 1
write-host "." -nonewline
[System.Windows.Forms.Application]::DoEvents()
} while ($obj -eq $null)
write-host
}
The example generates 'Opening report ....', but the loop never exits.
I should probably use a call-back or delegate, but I'm not sure of the pattern in this situation.
What am I missing?
You should be using Write-Progress to inform the user of the run status of your process and update it as events warrant.
If you want to do some "parallel" computing with powershell use jobs.
$job = Start-Job -ScriptBlock {Open-File $file} -ArgumentList $file
while($job.status -eq 'Running'){
Write-Host '.' -NoNewLine
}
Here is what I think about Write-Host.
The script runs in sequence. When you input a $null object, the while condition will always be true. The function will continue forever until something breaks it (which never happends in your script.
First then will it be done with the function and continue with your next lines:
# long-running
$rpt = rpt-open -path "C:\Documents and Settings\foobar\Desktop\Calendar.rpt"
Summary: Your while loop works like while($true) atm = bad design