Recently, a client of mine was upgrading their SolarWinds servers and they were still using the old Scheduled Unmanage utility. You remember the one, right? It produced XML files and .cmd files that were run via scheduled tasks. We wanted to move them to using the SDK and the Invoke-SwisVerb command that is part of the SwisPowershell module.
There were a couple of requirements:
1) Unmanage schedules needed to be executed daily, every weekday, only on weekends, monthly on the Xth weekday of the month, etc.
2) Script had to use a centralized input file to better track what was being unscheduled and when
3) Results needed to be logged
After a fair bit of scripting (ask zackm, I'm a hack scripter!) this is what I came up with and I thought I'd share it with the community. Why? Because so much of what I've learned that enabled me to build this script came from people in the THWACK community, so this is me paying it forward.
The Input File
This script reads a CSV to get the details of what is going to be unmanaged and when it will be unmanaged. There comments in the script outline the requirements for the CSV, but here's a snippet.
Node,Month,WeekofMonth,Day,StartTime,Duration
Node1,None,0,Wed,01:30,0:15:00
Node2,ALL,1,Wed,05:30,1:00:00
Node3,Jan,1,Sun,01:00,1:00:00
Node3,Jul,1,Sun,01:00,1:00:00
Node 1 will be unmanaged every Wednesday at 1:30AM for 15 minutes
Node 2 will be unmanaged on the 1st Wednesday of every month at 5:30AM for 1 hour
Node 3 will be unmanaged on the 1st Sunday of January and July (2 entries) at 1AM for 1 hour
The Script
It is best practice to encrypt your password when using a service account to execute those scripts. This snippet will produce an encrypted file containing the password for the service account.
# Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
# $filename = "C:\Scripts\SolarWinds\securepass.txt"
# (Get-Credential).Password | ConvertFrom-SecureString | Out-File $filename
# write-host "Password is secured in $filename"
This next section allows you to use a hard-coded username/password (for testing only) or to configure the script to use the password generated in the previous section.
#region Username and Password
#Use next line for credentials to log into SolarWinds Orion for testing purposes only. PICK ONE!
# Remember to comment out lines 119 and 122 through 124 if testing
#$Username = Read-Host -Prompt ('Please enter SolarWinds admin account (domain\username)')
#$username = 'adminjosh'
#Use next 2 lines for credentials to log into SolarWinds Orion for testing purposes only
#$Pass = Read-Host -Prompt ('Please enter SolarWinds admin password') -AsSecureString
#$Pass = ConvertTo-SecureString -String $RawPassword -AsPlainText -Force
#Set the username for the service account here.
$username = '<localadmin>'
#Use this section if you have encrypted a password. Update the path and name of the file.
$File = "I:\Scripts\SolarWinds\securepass.txt"
$pass = Get-Content $File | ConvertTo-SecureString
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Username, $pass
#endregion
This section defines the queries that will be used to find the nodes and then to validate that we successfully set an unmanage. That last part is a bit ugly right now, to be honest I'll try and clean it up at a later date.
#region query definitions
$queryNodeID = "SELECT TOP 1 NodeID FROM Orion.Nodes oRN WHERE orN.caption like '$($l.Node)%' OR orN.IPAddress = '$($l.Node)'"
$queryValidate = "SELECT UnmanageFrom, UnmanageUntil FROM Orion.Nodes oRN WHERE orN.NodeID = $nodeID"
#endregion
This is the meat of the script. I first check for a NodeID. If no NodeID is returned, we'll just script to the end and try the next line in the CSV.
Then the script breaks out the time, builds $startDate and $endDate and then cycles through checking the various scenarios to see which one matches. If nothing matches, the CSV file was problematic and we skip that entry and dump out the details to our log file.
I'll try and answer questions. If you see any errors that I made or have any suggestions for me, feel free to drop a note too!
Happy scripting!
#region Script Body
FOREACH ($l in $list) {
# Get the NodeID from the database $nodeID = Get-SwisData -SwisConnection $swis -Query $queryNodeID IF ( !$nodeID ) { Write-Log -Path $fullpath -Message "No matching NodeID for $($l.node) found. Please check the node caption and/or IP address" Write-Host "No matching NodeID for $($l.node) found. Please check the node caption and/or IP address" } ELSE { Write-Host "Starting on $($l.node)" # Parse the specified duration into its component parts $hours = ($l.Duration -split ":")[0] $minutes = ($l.Duration -split ":")[1] $seconds = ($l.Duration -split ":")[2] #Not currently used Write-Host "Duration = $hours hours and $minutes mins" Write-Host "Found nodeid = $nodeID" # Sets the start time for the current date by starting at midnight and adding the components of the start time $startDate = (Get-Date 00:00:00).AddHours($hours).AddMinutes($minutes) # Build a time-span with each of the components $timespan = New-TimeSpan -Hours $hours -Minutes $minutes # Calculate the end date $endDate = $startDate + $timespan # Stanze checks for WEEKLY recurring maintenance (month = 'None') AND WeekofMonth is either 0 or empty. IF ($l.Month -eq "None" -and ( $l.WeekofMonth -eq 0 -or !$l.WeekofMonth ) ) { Write-Log -Path $fullpath -Message "$($l.Node) will be suppressed from $startdate to $enddate" Write-Host "$($l.Node) will be suppressed from $startdate to $enddate" Invoke-SwisVerb -SwisConnection $swis -EntityName Orion.Nodes -Verb Unmanage -Arguments @( "N:$nodeID", $startDate, $endDate, "false" ) | Out-Null IF ( ( Get-SwisData -SwisConnection $swis -Query $queryValidate ).UnmanageFrom.DayofYear -eq (Get-date).DayOfYear ) { Write-Log -Path $fullpath -Message "$($l.Node) is unmanaged" Write-Host "$($l.Node) is unmanaged" } ELSE { Write-Log -Path $fullpath -Message "$($l.Node) failed to unmanage" Write-Host "$($l.Node) failed to set the scheduled unmanage" } } # Checks to see if the maintenance happens every week day ELSEIF ($l.Day -eq "Weekdays" -and (Get-Date -format ddd) -ne "Sat" -and (Get-Date -Format ddd) -ne "Sun") { Write-Log -Path $fullpath -Message "$($l.Node) will be suppressed from $startdate to $enddate" Write-Host "$($l.Node) will be suppressed from $startdate to $enddate" Invoke-SwisVerb -SwisConnection $swis -EntityName Orion.Nodes -Verb Unmanage -Arguments @( "N:$nodeID", $startDate, $endDate, "false" ) | Out-Null IF ( ( Get-SwisData -SwisConnection $swis -Query $queryValidate ).UnmanageFrom.DayofYear -eq (Get-date).DayOfYear ) { Write-Log -Path $fullpath -Message "$($l.Node) is unmanaged" Write-Host "$($l.Node) is unmanaged" } ELSE { Write-Log -Path $fullpath -Message "$($l.Node) failed to unmanage" Write-Host "$($l.Node) is failed to unmanage" } } # Checks to see if the maintenance happens only on weekends ELSEIF ($l.Day = "Weekends" -and ( (Get-Date -format ddd) -eq "Sat" -or (Get-Date -Format ddd) -eq "Sun") ) { Write-Log -Path $fullpath -Message "$($l.Node) will be suppressed from $startdate to $enddate" Write-Host "$($l.Node) will be suppressed from $startdate to $enddate" Invoke-SwisVerb -SwisConnection $swis -EntityName Orion.Nodes -Verb Unmanage -Arguments @( "N:$nodeID", $startDate, $endDate, "false" ) | Out-Null IF ( ( Get-SwisData -SwisConnection $swis -Query $queryValidate ).UnmanageFrom.DayofYear -eq (Get-date).DayOfYear ) { Write-Log -Path $fullpath -Message "$($l.Node) is unmanaged" Write-Host "$($l.Node) is unmanaged" } ELSE { Write-Log -Path $fullpath -Message "$($l.Node) failed to unmanage" Write-Host "$($l.Node) is failed to unmanage" } } # Assumes maintenance is not monthly and happens on a specific day of the week ELSEIF ($l.Day -eq (Get-Date -Format ddd) ) { Write-Log -Path $fullpath -Message "$($l.Node) will be suppressed from $startdate to $enddate" Write-Host "$($l.Node) will be suppressed from $startdate to $enddate" Invoke-SwisVerb -SwisConnection $swis -EntityName Orion.Nodes -Verb Unmanage -Arguments @( "N:$nodeID", $startDate, $endDate, "false" ) | Out-Null IF ( ( Get-SwisData -SwisConnection $swis -Query $queryValidate ).UnmanageFrom.DayofYear -eq (Get-date).DayOfYear ) { Write-Log -Path $fullpath -Message "$($l.Node) is unmanaged" Write-Host "$($l.Node) is unmanaged" } ELSE { Write-Log -Path $fullpath -Message "$($l.Node) failed to unmanage" Write-Host "$($l.Node) is failed to unmanage" } } # Stanza checks for every month (Month = ALL), a specific week of the month but not EVERY week (use weekly for that), AND a specific day of the week (eg First Monday of the month) ELSEIF ($l.Month -eq "ALL" -and $l.Day -eq (Get-Date -format ddd) -and $l.WeekofMonth -eq ( [math]::Ceiling(($d.Day+(($d.AddDays(-($d.Day-1))).Day.value__)-7)/7+1) ) ) { Write-Log -Path $fullpath -Message "$($l.Node) will be suppressed from $startdate to $enddate" Write-Host "$($l.Node) will be suppressed from $startdate to $enddate" Invoke-SwisVerb -SwisConnection $swis -EntityName Orion.Nodes -Verb Unmanage -Arguments @( "N:$nodeID", $startDate, $endDate, "false" ) | Out-Null } # Stanze checks for a specific month then matches day of week AND week of month (eg Third Monday in August @ 1:00AM for 2 hours would be <node>,Aug,3,Wed,02:00,2:00:00 ELSEIF ($l.Month -eq ( Get-Date -Format MMM) -and $l.Day -eq (Get-Date -format ddd) -and $l.WeekofMonth -eq ( [math]::Ceiling(($d.Day+(($d.AddDays(-($d.Day-1))).Day.value__)-7)/7+1) ) ) { Write-Log -Path $fullpath -Message "$($l.Node) will be suppressed from $startdate to $enddate" Write-Host "$($l.Node) will be suppressed from $startdate to $enddate" Invoke-SwisVerb -SwisConnection $swis -EntityName Orion.Nodes -Verb Unmanage -Arguments @( "N:$nodeID", $startDate, $endDate, "false" ) | Out-Null } ELSE { Write-Log -Path $fullpath -Message "$($l.node) has an invalid values. Please validate the source file" Write-Host "$($l.node) has an invalid Day of Week value. Please validate the source file" } }
}
#endregion