This tool has 2 possible options;
Reset 1 Access Point
Reset ALL Access Points at a site
For the sake of record keeping, I have a function that sends an email alert when either of these events occur.
Reset a single AP:
Function Manage-APReset {
Write-Verbose "Function start: Manage-APReset"
Write-Host "Executing access point reset for $apName .."
IF($controllerName -eq $null) {
Error-NoCon }
else {
## Establish connection(s)
[string]$cUser = "srv-****"
$cPassword = ConvertTo-SecureString -String "X***********" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential ($cUser, $cPassword)
Write-Host "Establishing SSH connection to Cisco Controller $controllerName"
New-SSHSession -ComputerName $controllerName -Credential $cred
$session = Get-SSHSession -Index 0
$stream = $session.Session.CreateShellStream("PS-SSH", 0, 0, 0, 0, 100)
sleep 4
Write-Host "Connected. Authenticating for SSH Stream.."
## Invoke login
$stream.WriteLine('srv-*****')
sleep 3
$stream.WriteLine('X********j8')
sleep 2
$Stream.Read()
Write-Host "Authenticated!"
## Invoke commands
$stream.WriteLine("config ap reset $apName")
sleep 2
$stream.WriteLine('y')
sleep 3
$stream.Read()
Write-Host "$apName has been reset successfully. Please allow up to 5 minutes to come back up"
Admin-SendAlert($event = 1)
Remove-SSHSession -SessionId 0,1,2,3,4,5,6,7,8,9
Repeat
}
}
You'll notice at the end I call Admin-SendAlert which handles the email alert. During this time I pass $event = 1 to allow Admin-SendAlert to know what condition is occurring.
Function Admin-SendAlert {
Write-Verbose "Function started: Admin-SendAlert"
## Event 1: Single AP Reset Successfully
if($event = 1) {
$eventSub = "Single: $apName has been reset"
$eventBod = "$apName has been reset successfully by $tech`n Reason Summary: $reasonSum"
}
if($event = 2) {
$eventSub ="Full Store Reset: $Store All APs Reset"
$eventBod = "The following APs have been reset at Store $Store by user $tech. `n`nAll APs:`n $apArray`n Reason Summary: $reasonSum"
}
Send-MailMessage -To "CSOC <blank#email.com>" -From "AP Manager Beta <srv-blank#email.com>" -Subject $eventSub -SmtpServer smtp.email.com -Body $eventBod
}
I don't believe this is how this should be handled as the value of $event remains whichever comes first. How should I be doing this?
Your main issue is that you are using the assignment operator in place of the equality comparator. So if($event = 1) should be if($event -eq 1)
I see room for improvement as well for you if clauses. You are checking the numerical value of $event. It will only be one of those values. Never two. Your if clauses are mutually exclusive yet you attempt to evaluate both -eq 1 and -eq 2. Not the best idea as it make for muddy code. You should be using if and elseif to contain it in the same block
if($event -eq 1) {
# Stuff happens
} elseif($event -eq 2) {
# Stuff happens
} else {
# Last resort
}
Further to that, if yourself with too many elseif clauses, you would be better off using switch
switch($event){
1 {
# Stuff happens
break
}
2 {
# Stuff happens
break
}
default {
# Last resort
}
}
Note: if you don't use break it will evaluate all switch conditions and execute all that match.
Related
I'm using try-catch-finally blocks in my account expiration notification email and the if condition in the finally block isn't considered:
Write-Host $sName " : Selected to receive email: password will expire in "$days
if (($emailaddress) -ne $null) {
try {
Send-Mailmessage -smtpServer $SMTPServer -from $MailSender -to $emailaddress -subject $subject2 -body $EmailBody -bodyasHTML -priority High -Encoding $textEncoding -ErrorAction Stop
}
catch {
write-host "Error: Could not send email to $recipient via $smtpServer"
$sent = "Send fail"
$countfailed++
}
finally {
if ($error.Count -eq 0) {
write-host "Sent email for $sName to $emailaddress"
$countsent0++
}
}
} else {
Write-Host "$dName ($sName) has no email address."
$sent = "No"
$countnotsent++
}
Expecting $countsent0 to increment and $sent to be set to the appropriate message. The catch block works ($countfailed increments and $sent is set to "Send fail"). The last else statement after the finally block works as well (if there is no email address for the account $countnotsent increments and $sent is set to "No").
The automatic $Error variable is a running log of all errors that have occurred in the entire session so far, so unless you run $Error.Clear() beforehand, you may get false positives with if ($error.Count -eq 0) { ... }
However, since you may not want to erase this log for the entire session, consider using a simple Boolean variable to indicate whether an error was caught or not.
A simplified example:
$countsent0 = 0
$ok = $false # Helper Boolean variable.
try {
1 / 0 # Simulate an error that can be caught.
$ok = $true # At the end of this block, signal that no error occurred.
}
catch {
Write-Warning 'oops!'
}
finally {
if ($ok) { $countsent0++; Write-Host 'All''s well'. }
}
"`$countsent0: $countsent0"
However, given that your catch block doesn't abort execution, you don't even need a finally block:
$countsent0 = 0
try {
1 / 0 # Simulate an error that can be caught.
# Getting here implies success.
$countsent0++
}
catch {
Write-Warning 'oops!'
}
"`$countsent0: $countsent0"
I have a script that's designed to ping another host across a site-to-site VPN tunnel every minute. After 10 minutes, it checks the average uptime of test-connection, and if it falls below a certain threshold, it sends a Teams message telling us to check things out.
This works perfectly well when I manually run the script in situ, however, when I leave it to run as a background job, it isn't sending the Teams messages.
My question is this: as a relatively new sysadmin, the tools in my toolkit are pretty limited. Does anyone have a good tip for where I should start looking, to troubleshoot this issue? To rule out potential problems with my script, I've also included it below. But I suspect the issue is more to do with leaving the script to run on a server that I then log out of. The server in question is running Windows Server 2012 (yes I know, migration is on my to-do list).
Import-Module message_module # a module i wrote to wrap messages to Teams webhooks (included below)
# this array will accept output values from the ongoing test
$test_table = new-object system.collections.arraylist
# this index counts how many times we've checked recently
[int32[]]$test_index = 1
# our desired threshold for uptime / response
$uptime = .8
# how many minutes to count before testing
$count_length = 10
# IP to ping
$ping_ip = 'XXX.XXX.XXX.XXX'
$test_ip = '142.251.33.110' # google.com, used for testing
# here's the actual function that does the pinging and puts values in the arraylist
function Ping-VPN {
$ping_host = test-connection $ping_ip -erroraction silentlycontinue
if ( $ping_host ) {
$test_table.add(1) > $null
} else {
$test_table.add(0) > $null
}
}
# this function calculates the average of the values in test_table, and then clears them
function Get-Average-Uptime {
$sum = 0
foreach ($entry in $test_table) {
$sum += $entry
}
$avg = $sum / $test_table.count
return $avg
}
function Main-Loop {
while ( $test_index -lt $count_length ) {
Ping-VPN
$test_index += 1
start-sleep -seconds 60
}
$avguptime = Get-Average-Uptime
$test_table.clear
if ( $avguptime -lt $uptime ) {
$title = "XXX/XXX VPN Down"
$message = "XXXXXX response to ping from XXXXXXX at less than desired rate. Please investigate."
Send-TeamsMessage -Message $message -Title $title
start-sleep -seconds 3600 # sleep for an hour, to avoid spamming us
}
$test_index = 0 # restart the testing interval
Main-Loop
}
Main-Loop
And the module code:
function Send-TeamsMessage {
Param(
[Parameter(Position = 0, Mandatory = $true)][String]$Message,
[Parameter(Position = 1, Mandatory = $true)][String]$Title
)
$JSONBody = [PSCustomObject][Ordered]#{
"#type" = "MessageCard"
"#context" = "http://schema.org/extensions"
"themeColor" = '0078D7'
"title" = $Title
"text" = $Message
}
$TeamMessageBody = ConvertTo-Json $JSONBody -Depth 100
$parameters = #{
"URI" = 'XXXXXXXX (webhook URI)'
"Method" = 'POST'
"Body" = $TeamMessageBody
"ContentType" = 'application/json'
}
Invoke-RestMethod #parameters | Out-Null
}
Export-ModuleMember -Function Send-TeamsMessage
Right now, I'm calling the main file with:
start-job -file C:\path\to\file.ps1
Then minimizing the terminal and disconnecting from the server. I suspect the problem is something to do with this, that I'm missing something really obvious.
As it turns out, this question is quite similar to another, though they're phrased very differently.
Basically, what I need to do is to run the script as NT AUTHORITY\System on startup. Run an infinite command on Windows Server even if someone is logged out
I implemented a powershell script, which assigns Exchange settings to our user mailboxes (Exchange 2016). As we have a lot of mailboxes and assigning settings is slow, the script would run more then 15 hours. However after about 10 hours I get the following error:
Processing data for a remote command failed with the following error message: Error occurred during the Kerberos response.
[Server=XXXXX, TimeStamp = 74/2018 01:25:49]
For more information, see the about_Remote_Troubleshooting Help topic.
At C:\Users\ACCOUNT\AppData\Local\Temp\tmp_cj3akhk4.osq\tmp_cj3akhk4.osq.psm1:77943 char:9
+ $steppablePipeline.End()
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (XXXX:String) [], PSRemotingTransportException
+ FullyQualifiedErrorId : JobFailure
+ PSComputerName : XXXX
My script retires the operation and after two retries (which fail) an authentication prompt is shown. There I can enter the password of the service account and the script continues. However this dialog is only visible if I run the script in a PS command prompt. If the script is started as Windows Task, it just hangs and does not continue.
The connection to Exchange is opened and imported with the following code. The code can either connect to our on premises Exchange or Exchange online based on the passed parameter. The problem is currently only happening, when connected to our local (on premises) Exchange infrastructure.
Function Connect-Exchange{
PARAM(
[parameter(Mandatory=$false)]
[String]$TargetExchange = 'Local'
)
BEGIN{}
PROCESS{
if ($ExchangeSessionInfo.Session -and $ExchangeSessionInfo.Type -eq $TargetExchange -and $ExchangeSessionInfo.Session.State -eq 'Opened'){
# Nothing to do, we are already connected.
Write-Log "Exchange connection type $($TargetExchange) already established, nothing to do."
} else {
if ($ExchangeSessionInfo.Session -and $ExchangeSessionInfo.Type -ne $TargetExchange -and $ExchangeSessionInfo.Session.State -eq 'Opened'){
# We have a open session with the wrong type. We close it.
Remove-PSSession $ExchangeSessionInfo.Session
$ExchangeSessionInfo.Session = $null
$ExchangeSessionInfo.Status = 'undefined'
$ExchangeSessionInfo.Type = ''
}
# We close all other existing Exchange sessions we created.
get-pssession -Name "Exchange" -ErrorAction SilentlyContinue | remove-pssession
# Now connect to the requestes Exchange infrastructure and import session.
$Connected = $False
$RetryCount = 5
do{
try {
If ($TargetExchange -eq 'Local'){
$ExchangeServer = Get-Random -InputObject $LocalExchangeConfig.ExchangeServers
$ExchangeSessionInfo.Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://$($ExchangeServer)/PowerShell/" -Credential $EOCredentials -Authentication Kerberos -Name "Exchange"
} else {
$ExchangeSessionInfo.Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri 'https://ps.protection.outlook.com/powershell-liveid/' -Credential $EOCredentials -Authentication Basic -AllowRedirection -Name "Exchange"
}
$Res = Import-PSSession $ExchangeSessionInfo.Session -WarningAction SilentlyContinue -AllowClobber
# Store Exchange status in session variable.
$Connected = $True
$ExchangeSessionInfo.Status = 'connected'
$ExchangeSessionInfo.Type = $TargetExchange
} catch {
$err = Write-Error -err $error -msg "Could not connect to Exchange server type '$($TargetExchange)' (Retries left: $($RetryCount))." -Break $false
get-pssession -Name "Exchange" -ErrorAction SilentlyContinue | remove-pssession
$RetryCount -= 1
}
} while (!$Connected -and ($RetryCount -gt 0))
# If we do not have connection here, this is an error.
if (!$Connected) {
$ExchangeSessionInfo.Session = $null
$ExchangeSessionInfo.Status = 'undefined'
$ExchangeSessionInfo.Type = ''
throw "No connection to Exchange server (type: $($TargetExchange)) could be established."
} else {
# Get list of available mailbox DBs including mailbox count and create hashtable to store statistics. We only have to get it the first time.
if (($MailboxDBList.count -eq 0) -and ($TargetExchange -eq 'Local')){
Write-Log "Getting current Exchange DB configuration and mailbox count. Takes a moment."
$MailboxDBList = Get-MailboxDBCount -Type $LocalExchangeConfig.DistributeMailboxes
}
}
}
}
END{
return $ExchangeSessionInfo
}
}
The following code is applying a predefined set of Exchange settings:
...
$TryCount = 0
$Done = $false
do{
# It takes a while after enabling mailbox until settings can be applied. So we need to retry.
try{
# If we need to execute a setting several times.
if ($MailboxSetting.LoopOver){
# We have a loop value (array).
foreach ($LoopValue in $MailboxSetting.LoopOver){
# Copy parameter as we have to change a value (loop value).
$TempParams = $Params.PsObject.Copy()
#($Params.getenumerator()) |? {$_.Value -match '#LOOPVALUE#'} |% {$TempParams[$_.Key]=$LoopValue}
$res = & $MailboxSetting.Command -ErrorAction Stop #TempParams -WhatIf:$RunConfig.TestMode
}
} else {
# THE PROBLEM HAPPENS HERE
$res = & $MailboxSetting.Command -ErrorAction Stop #Params -WhatIf:$RunConfig.TestMode
}
# Write-Log "Setting command $($MailboxSetting.Command) executed successfully"
$Done = $true
} catch{
$tryCount++
$res = Write-Error -err $error -msg "Error applying mailbox settings, account: $($AccountDetails.sAMAccountName), retry count: $($TryCount)" -Break $false
Start-Sleep -s $(($Retires-$TryCount) * 5)
}
} while ((!$done) -and ($tryCount -lt $Retires))
...
I am sure the error is not related to the code, because the script runs for hours without a problem and applies all settings. However after a around 10 hours it seems the Kerberos ticket expires and then the script cannot longer access Exchange without a re-login.
Is there a way to keep the Kerberos ticket from expiring or renew it?
Any help would be appreciated.
I think you are hitting the domain security policy (group policy object - GPO) => security settings/account policy/Kerberos policy restriction.
The two valid options for you are:
Maximum lifetime for user ticket => the default value is 10 hours
Maximum lifetime for user ticket renewal => the default value is 7 days (this is the period within which the ticket can be renewed).
Is there a way to keep the Kerberos ticket from expiring or renew it?
For the first questions you "just" need to adjust the maximum lifetime for user ticket setting to value as you deem appropriate.
The second one is more tricky. I would just purge all kerberos tickets via the powershell. For more - viewing and purging cached kerberos tickets which would get you a new one.
If the ticket can be renewed you have to check the RENEABLE flag - you wan view it via kinit. Perhaps kinit -R could be enough for ticket renewal. (I did not do this my self) You could also renew it via kerberos for windows
Edit -- adding klist purge to purge all Kerberos tickets so it can be renewed.
As you have klist then you can purge all tickets via must be run in elevated powershell prompt
(all credits to JaredPoeppelman):
Get-WmiObject Win32_LogonSession | Where-Object {$_.AuthenticationPackage -ne 'NTLM'} | ForEach-Object {klist.exe purge -li ([Convert]::ToString($_.LogonId, 16))}
Then check if your TGT was updated via:
klist tgt
Note: you must use FQDN name everywhere!
Thanks for your suggestion. In a first try I will extend my code as follows and try to reestblisch a new Exchange connection. Needs 10 h runnig the script in order to see if this works.
I am not able to influence the domain security Policy, additionally as I do not know how long the script runs, it will be difficult to set a value.
On my Windows 2016 the command "kinit" ist not recognized. Possibly I need to install additional modules/roles.
...
$TryCount = 0
$Done = $false
do{
# It takes a while after enabling mailbox until settings can be applied. So we need to retry.
try{
# If we need to execute a setting several times.
if ($MailboxSetting.LoopOver){
# We have a loop value (array).
foreach ($LoopValue in $MailboxSetting.LoopOver){
# Copy parameter as we have to change a value (loop value).
$TempParams = $Params.PsObject.Copy()
#($Params.getenumerator()) |? {$_.Value -match '#LOOPVALUE#'} |% {$TempParams[$_.Key]=$LoopValue}
$res = & $MailboxSetting.Command -ErrorAction Stop #TempParams -WhatIf:$RunConfig.TestMode
}
} else {
$res = & $MailboxSetting.Command -ErrorAction Stop #Params -WhatIf:$RunConfig.TestMode
}
# Write-Log "Setting command $($MailboxSetting.Command) executed successfully"
$Done = $true
} catch{
$tryCount++
$res = Write-Error -err $error -msg "Error applying mailbox settings, account: $($AccountDetails.sAMAccountName), retry count: $($TryCount)" -Break $false
Start-Sleep -s $(($Retires-$TryCount) * 5)
try{
# We may have lost the Kerberos ticket, reconnect to Exchange.
$ConnectionType = $ExchangeSessionInfo.Type
Disconnect-Exchange
Connect-Exchange -TargetExchange $ConnectionType
} catch {}
}
} while ((!$done) -and ($tryCount -lt $Retires))
...
$exceptionList = Get-Content C:\Users\Dipen\Desktop\Exception_List.txt
$ReceiveLocations = Get-WmiObject MSBTS_ReceiveLocation -Namespace 'root\MicrosoftBizTalkServer' -Filter '(IsDisabled = True)' |
Where-Object { $exceptionList -notcontains $_.Name }
# Exit the script if there are no disabled receive locations
if ($ReceiveLocations.Count -eq 0)
{
exit
}
Example:
and
$mailBodyPT = ""
$mailTextReportPT = "There are: "
[STRING]$Subject = $SubjectPrefix + $BizTalkGroup
$mailTextReportPT += "in the BizTalk group: " + $BizTalkGroup + "."
#Send mail
foreach ($to in $EmailTo)
{
$Body = $HTMLmessage
#$SMTPClient = New-Object Net.Mail.SmtpClient($PSEmailServer)
$message = New-Object Net.Mail.MailMessage($from, $to, $Subject, $Body)
$message.IsBodyHtml = $true;
$SMTPClient.Send($message)
}
Question: when all RLs have the status "disabled" and all of these RLs are included in the exception list the value of the variable $ReceiveLocations should be false and I need to stop further processing in my script. (do nothing if all RLs are found in exception list, just exit)
But I'm still getting false email alerts. How can we set logic for not getting email alerts if there were no extra RLs found in $ReceiveLocations?
The value of the variable $ReceiveLocations is $null when your Get-WmiObject statement doesn't return results. $null doesn't have a property Count, hence the check $ReceiveLocations.Count -eq 0 fails and your script doesn't terminate before sending an e-mail.
You can avoid this issue in a number of ways, e.g. by putting $ReceiveLocations in the array subexpression operator:
if (#($ReceiveLocations).Count -eq 0) {
exit
}
or you could use the way PowerShell interprets values in boolean expressions (non-empty arrays become $true, $null becomes $false):
if (-not $ReceiveLocations) {
exit
}
I'm using PowersHell to automate iTunes but find the error handling / waiting for com objects handling to be less than optimal.
Example code
#Cause an RPC error
$iTunes = New-Object -ComObject iTunes.Application
$LibrarySource = $iTunes.LibrarySource
# Get "playlist" objects for main sections
foreach ($PList in $LibrarySource.Playlists)
{
if($Plist.name -eq "Library") {
$Library = $Plist
}
}
do {
write-host -ForegroundColor Green "Running a loop"
foreach ($Track in $Library.Tracks)
{
foreach ($FoundTrack in $Library.search("$Track.name", 5)) {
# do nothing... we don't care...
write-host "." -nonewline
}
}
} while(1)
#END
Go into itunes and do something that makes it pop up a message - in my case I go into the Party Shuffle and I get a banner "Party shuffle automatically blah blah...." with a "Do not display" message.
At this point if running the script will do this repeatedly:
+ foreach ($FoundTrack in $Library.search( <<<< "$Track.name", 5)) {
Exception calling "Search" with "2" argument(s): "The message filter indicated
that the application is busy. (Exception from HRESULT: 0x8001010A (RPC_E_SERVER
CALL_RETRYLATER))"
At C:\Documents and Settings\Me\My Documents\example.ps1:17 char:45
+ foreach ($FoundTrack in $Library.search( <<<< "$Track.name", 5)) {
Exception calling "Search" with "2" argument(s): "The message filter indicated
that the application is busy. (Exception from HRESULT: 0x8001010A (RPC_E_SERVER
CALL_RETRYLATER))"
At C:\Documents and Settings\Me\My Documents\example.ps1:17 char:45
If you waited until you you had a dialog box before running the example then instead you'll get this repeatedly:
Running a loop
You cannot call a method on a null-valued expression.
At C:\Documents and Settings\Me\example.ps1:17 char:45
+ foreach ($FoundTrack in $Library.search( <<<< "$Track.name", 5)) {
That'll be because the $Library handle is invalid.
If my example was doing something important - like converting tracks and then deleting the old ones, not handling the error correctly could be fatal to tracks in itunes.
I want to harden up the code so that it handles iTunes being busy and will silently retry until it has success. Any suggestions?
Here's a function to retry operations, pausing in between failures:
function retry( [scriptblock]$action, [int]$wait=2, [int]$maxRetries=100 ) {
$results = $null
$currentRetry = 0
$success = $false
while( -not $success ) {
trap {
# Set status variables at function scope.
Set-Variable -scope 1 success $false
Set-Variable -scope 1 currentRetry ($currentRetry + 1)
if( $currentRetry -gt $maxRetries ) { break }
if( $wait ) { Start-Sleep $wait }
continue
}
$success = $true
$results = . $action
}
return $results
}
For the first error in your example, you could change the inner foreach loop like this:
$FoundTracks = retry { $Library.search( "$Track.name", 5 ) }
foreach ($FoundTrack in $FoundTracks) { ... }
This uses the default values for $wait and $maxRetries, so it will attempt to call $Library.search 100 times, waiting 2 seconds between each try. If all retries fail, the last error will propagate to the outer scope. You can set $ErrorActionPreference to Stop to prevent the script from executing any further statements.
COM support in PowerShell is not 100% reliable. But I think the real issue is iTunes itself. The application and COM model wasn't designed, IMO, for this type of management. That said, you could implement a Trap into your script. If an exception is raised, you could have the script sleep for a few seconds.
Part of your problem might be in how $Track.name is being evaluated. You could try forcing it to fully evaluate the name by using $($Track.name).
One other thing you might try is using the -strict parameter with your new-object command/