I'm doing a BITS transfer of daily imagery from a web server and I keep getting random drops during the transfer.
As it's cycling through the downloads I get the occasional "The connection was closed prematurely" or "An error occurred in the secure channel support". There are about 180 images in each folder and this happens for maybe 5-10% of them. I need to retry the download for those that didn't complete.
My code to do so follows - my imperfect work-around is to run the loop twice but I'm hoping to find a better solution.
# Set the URL where the images are located
$url = 'https://www.nrlmry.navy.mil/archdat/global/stitched/MoS_2/navgem/wind_waves/latest/'
# Set the local path where the images will be stored
$path = 'C:\images\Wind_Waves\latest\'
# Create a list of all assets returned from $url
$site = Invoke-WebRequest -UseBasicParsing -Uri $url
# Create a table subset from the $site of all files returned with a .jpg extension
$table = $site.Links | Where-Object{ $_.tagName -eq 'A' -and $_.href.ToLower().EndsWith("jpg") }
# Create a list of all href items from the table & call it $images
$images = $table.href
# Enumerate all of the images - for troubleshooting purposes - can be removed
$images
# Check to make sure there are images available for download - arbitrarily picked more than 2 $images
if($images.count -gt 2){
# Delete all of the files in the "latest" folder
Remove-Item ($path + "*.*") -Force
# For loop to check to see if we already have the image and, if not, download it
ForEach ($image in $images)
{
if(![System.IO.File]::Exists($path + $image)){
Write-Output "Downloading: " $image
Start-BitsTransfer -Source ($url + $image) -Destination $path -TransferType Download -RetryInterval 60
Start-Sleep 2
}
}
Get-BitsTransfer | Where-Object {$_.JobState -eq "Transferred"} | Complete-BitsTransfer
} else {
Write-Output "No images to download"}
I don't see any error handling in your code to resume/retry/restart on fail.
Meaning why is there no try/catch in the loop or the Get?
If the Get is on per download job in the loop, why is it outside the loop?
Download is the default for TransferType, so no need to specify, it normally will generate an error if you do.
So, something like this. I did test this, but never got a fail. Yet, I have a very high-speed speed internet connection. If you are doing this inside an enterprise, edge devices (filters, proxies, could also be slowing things down, potentially forcing timeouts.)
$url = 'https://www.nrlmry.navy.mil/archdat/global/stitched/MoS_2/navgem/wind_waves/latest/'
$path = 'D:\Temp\images\Wind_Waves\latest'
$site = Invoke-WebRequest -UseBasicParsing -Uri $url
# Create a table subset from the $site of all files returned with a .jpg extension
$table = $site.Links |
Where-Object{
$_.tagName -eq 'A' -and
$_.href.ToLower().EndsWith('jpg')
}
<#
# Create a list of all href items from the table & call it $images
Enumerate all of the images - for troubleshooting purposes - can be removed
Assign and display using variable squeezing
#>
($images = $table.href)
<#
Check to make sure there are images available for download - arbitrarily
picked more than 2 $images
#>
if($images.count -gt 2)
{
Remove-Item ($path + '*.*') -Force
ForEach ($image in $images)
{
Try
{
Write-Verbose -Message "Downloading: $image" -Verbose
if(![System.IO.File]::Exists($path + $image))
{
$StartBitsTransferSplat = #{
Source = ($url + $image)
Destination = $path
RetryInterval = 60
}
Start-BitsTransfer #StartBitsTransferSplat -ErrorAction Stop
Start-Sleep 2
}
Get-BitsTransfer |
Where-Object {$PSItem.JobState -eq 'Transferred'} |
Complete-BitsTransfer
}
Catch
{
$PSItem.Exception.Message
Write-Warning -Message "Download of $image not complete or failed. Attempting a resume/retry" -Verbose
Get-BitsTransfer -Name $image | Resume-BitsTransfer
}
}
}
else
{
Write-Warning -Message 'No images to download'
$PSItem.Exception.Message
}
See the help files
Resume-BitsTransfer
Module: bitstransfer Resumes a BITS transfer job.
# Example 1: Resume all BITS transfer jobs owned by the current user
Get-BitsTransfer | Resume-BitsTransfer
# Example 2: Resume a new BITS transfer job that was initially suspended
$Bits = Start-BitsTransfer -DisplayName "MyJob" -Suspended
Add-BitsTransfer -BitsJob $Bits -ClientFileName C:\myFile -ServerFileName http://www.SomeSiteName.com/file1
Resume-BitsTransfer -BitsJob $Bits -Asynchronous
# Example 3: Resume the BITS transfer by the specified display name
Get-BitsTransfer -Name "TestJob01" | Resume-BitsTransfer
Here's a somewhat modified version of the above code. It appears the BITS transfer job object goes away when the error occurs, so there is no use in trying to find/resume that job. Instead, I wrapped the entire Try-Catch block in a while loop with an exit when the file is downloaded.
$url = 'https://www.nrlmry.navy.mil/archdat/global/stitched/MoS_2/navgem/wind_waves/latest/'
$path = 'D:\Temp\images\Wind_Waves\latest'
$site = Invoke-WebRequest -UseBasicParsing -Uri $url
$MaxRetries = 3 # Initialize the maximum number of retry attempts.
# Create a table subset from the $site of all files returned with a .jpg extension
$table = $site.Links |
Where-Object {
$_.tagName -eq 'A' -and
$_.href.ToLower().EndsWith('jpg')
}
<#
# Create a list of all href items from the table & call it $images
Enumerate all of the images - for troubleshooting purposes - can be removed
Assign and display using variable squeezing
#>
($images = $table.href)
<#
Check to make sure there are images available for download - arbitrarily
picked more than 2 $images
#>
if ($images.count -gt 2) {
Remove-Item ($path + '*.*') -Force
ForEach ($image in $images) {
# Due to occasional failures to transfer, wrap the BITS transfer in a while loop
# re-initialize the exit counter for each new image
$retryCount = 0
while ($retryCount -le $MaxRetries){
Try {
Write-Verbose -Message "Downloading: $image" -Verbose
if (![System.IO.File]::Exists($path + $image)) {
$StartBitsTransferSplat = #{
Source = ($url + $image)
Destination = $path
RetryInterval = 60
}
Start-BitsTransfer #StartBitsTransferSplat -ErrorAction Stop
Start-Sleep 2
}
# To get here, the transfer must have finished, so set the counter
# greater than the max value to exit the loop
$retryCount = $MaxRetries + 1
} # End Try block
Catch {
$PSItem.Exception.Message
$retryCount += 1
Write-Warning -Message "Download of $image not complete or failed. Attempting retry #: $retryCount" -Verbose
} # End Catch Block
} # End While loop for retries
} # End of loop over images
} # End of test for new images
else {
Write-Warning -Message 'No images to download'
$PSItem.Exception.Message
} # End of result for no new images
Here is a combination of the code that postanote provided and a Do-While loop to retry the download up to 5x if an error is thrown.
$url = 'https://www.nrlmry.navy.mil/archdat/global/stitched/MoS_2/navgem/wind_waves/latest/'
$path = 'D:\Temp\images\Wind_Waves\latest'
$site = Invoke-WebRequest -UseBasicParsing -Uri $url
# Create a table subset from the $site of all files returned with a .jpg extension
$table = $site.Links |
Where-Object{
$_.tagName -eq 'A' -and
$_.href.ToLower().EndsWith('jpg')
}
<#
# Create a list of all href items from the table & call it $images
Enumerate all of the images - for troubleshooting purposes - can be removed
Assign and display using variable squeezing
#>
($images = $table.href)
<# Check to make sure there are images available for download - arbitrarily
picked more than 2 $images #>
if($images.count -gt 2)
{
Remove-Item ($path + '*.*') -Force
ForEach ($image in $images)
{
# Create a Do-While loop to retry downloads up to 5 times if they fail
$Stoploop = $false
[int]$Retrycount = "0"
do{
Try
{
Write-Verbose -Message "Downloading: $image" -Verbose
if(![System.IO.File]::Exists($path + $image))
{
$StartBitsTransferSplat = #{
Source = ($url + $image)
Destination = $path
RetryInterval = 60
}
Start-BitsTransfer #StartBitsTransferSplat -ErrorAction Stop
Start-Sleep 10
$Stoploop = $true
}
Get-BitsTransfer |
Where-Object {$PSItem.JobState -eq 'Transferred'} |
Complete-BitsTransfer
}
Catch
{
if ($Retrycount -gt 5){
$PSItem.Exception.Message
Write-Warning -Message "Download of $image not complete or failed." -Verbose
$Stoploop = $true
}
else {
Write-Host "Could not download the image, retrying..."
Start-Sleep 10
$Retrycount = $Retrycount + 1
}
}
}
While ($Stoploop -eq $false)
}
}
else
{
Write-Warning -Message 'No images to download'
$PSItem.Exception.Message
}
Related
I would like to ask question about how I should proceed or how I should fix the code.
My problem is that I need my code to write into the Path three different paths for Logstash, Kibana and ElasticSearch, but I have no idea how to do it. It returns always the same error about missing ")" error
Here's the whole code ¨
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[string]$NewLocation.GetType($ElasticSearch)
[string]$ElasticSearch = "C:\Elastic_Test_Server\elasticsearch\bin"
[string]$Kibana = "C:\Elastic_Test_Server\kibana\bin"
[string]$Logstash = "C:\Elastic_Test_Server\logstash\bin"
)
Begin
{
#Je potřeba spustit jako Administrátor
$regPath = "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
$hklm = [Microsoft.Win32.Registry]::LocalMachine
Function GetOldPath()
{
$regKey = $hklm.OpenSubKey($regPath, $FALSE)
$envpath = $regKey.GetValue("Path", "", [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
return $envPath
}
}
Process
{
# Win32API errory
$ERROR_SUCCESS = 0
$ERROR_DUP_NAME = 34
$ERROR_INVALID_DATA = 13
$NewLocation = $NewLocation.Trim();
If ($NewLocation -eq "" -or $NewLocation -eq $null)
{
Exit $ERROR_INVALID_DATA
}
[string]$oldPath = GetOldPath
Write-Verbose "Old Path: $oldPath"
# Zkontroluje zda cesta již existuje
$parts = $oldPath.split(";")
If ($parts -contains $NewLocation)
{
Write-Warning "The new location is already in the path"
Exit $ERROR_DUP_NAME
}
# Nová cesta
$newPath = $oldPath + ";" + $NewLocation
$newPath = $newPath -replace ";;",""
if ($pscmdlet.ShouldProcess("%Path%", "Add $NewLocation")){
# Přidá to přítomné session
$env:path += ";$NewLocation"
# Uloží do registru
$regKey = $hklm.OpenSubKey($regPath, $True)
$regKey.SetValue("Path", $newPath, [Microsoft.Win32.RegistryValueKind]::ExpandString)
Write-Output "The operation completed successfully."
}
Exit $ERROR_SUCCESS
}
Thank you for your help.
I really think you could simplify this a lot, unless I have misunderstood. Apologies, I am not currently on a Windows machine so can't test this.
function Add-ESPath {
# Create an array of the paths we wish to add.
$ElasticSearch = #(
"C:\Elastic_Test_Server\elasticsearch\bin",
"C:\Elastic_Test_Server\kibana\bin",
"C:\Elastic_Test_Server\logstash\bin"
)
# Collect the current PATH string and split it out in to an array
$CurrentPath = [System.Environment]::GetEnvironmentVariable("PATH")
$PathArray = $CurrentPath -split ";"
# Loop though the paths we wish to add.
foreach ($Item in $ElasticSearch) {
if ($PathArray -notcontains $Item) {
$PathArray += $Item
}
else {
Write-Output -Message "$Item is already a member of the path." # Use Write-Warning if you wish. I see it more as a notification here.
}
}
# Set the path.
$PathString = $PathArray -join ";"
Try {
[System.Environment]::SetEnvironmentVariable("PATH", $PathString)
exit 0
}
Catch {
Write-Warning -Message "There was an issue setting PATH on this machine. The path was:" # Use $env:COMPUTERNAME here perhaps instead of 'this machine'.
Write-Warning -Message $PathString
Write-Warning -Message $_.Exception.Message
exit 1
}
}
Add-ESPath
Perhaps you want to add some kind of log file rather than writing messages/warnings to the console. You can use Add-Content for this.
I long time ago i wrote some functions to add a path to system path + their is an check if the path is already inside the system path. And i also did an elevation check so when i use this function and i forgot to elevate my powershell that i get a warning. Its a different approach, I hope it will help you.
I only use the begin {} proccess{} statements for when i want to write a function that excepts pipeline inputs. So its if you want to write a function that will work as the following:
$paths = #("C:\Elastic_Test_Server\elasticsearch\bin", "C:\Elastic_Test_Server\kibana\bin")
$paths | my-append-these-to-system-path-function
Elevation check:
function G-AmIelevated($warningMessage){
if([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544")){
return $true
}else{
write-host "not elevated $warningMessage" -ForegroundColor Red
return $false
}
}
append something to system path with check if its already inside system path:
function G-appendSystemEnvironmentPath($str){
if(test-path $str){
if(!((Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path) -like "*$str*")){
write-host "`t $str exists...`n adding $str to environmentPath" -ForegroundColor Yellow
if(G-AmIelevated){
write-host `t old: (Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path).Path
Set-ItemProperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' `
-Name Path `
-Value "$((Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path).Path);$str"
write-host `t new: (Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path).Path
write-host `t restart the computer for the changes to take effect -ForegroundColor Red
write-host `t `$Env:Path is the merge of System Path and User Path This function set the system path
write-host `t $str appended to environmet variables. -ForegroundColor Green
}else{
write-host `t rerun ise in elevated mode -ForegroundColor Red
}
}else{
write-host "`t $str is in system environmenth path"
}
}else{
write-host `t $str does not exist
}
}
G-appendSystemEnvironmentPath -str "C:\Elastic_Test_Server\elasticsearch\bin"
G-appendSystemEnvironmentPath -str "C:\Elastic_Test_Server\kibana\bin"
G-appendSystemEnvironmentPath -str "C:\Elastic_Test_Server\logstash\bin"
I have this PowerShell script that I'm working on. CSV file is imported to get source and destination paths. The goal is to move files from a SFTP/FTP server into a destination and send an email report.
Task scheduler will run this code every hour. And if there's a new file, as email will be sent out.
It's almost done, but two things are missing:
Check if the file already exists and Body email seems empty: Getting the following error: Cannot validate argument on parameter 'Body'. The argument is null or empty. Provide an argument that is not
null or empty, and then try the command again.
I would like some assistance on how to check if the file exists and how to get this email if a new file was dropped and copied to the destination list
$SMTPBody = ""
$SMTPMessage = #{
"SMTPServer" = ""
"From" = ""
"To" = ""
"Subject" = "New File"
}
try {
# Load WinSCP .NET assembly
Add-Type -Path "C:\Program Files (x86)\WinSCP\WinSCPnet.dll"
# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property #{
Protocol = [WinSCP.Protocol]::sftp
HostName = ""
UserName = ""
Password = ""
PortNumber = "22"
FTPMode = ""
GiveUpSecurityAndAcceptAnySshHostKey = $true
}
$session = New-Object WinSCP.Session
try
{
# Connect
$session.Open($sessionOptions)
# Download files
$transferOptions = New-Object WinSCP.TransferOptions
$transferOptions.TransferMode = [WinSCP.TransferMode]::Binary
Import-Csv -Path "D:\FILESOURCE.csv" -ErrorAction Stop | foreach {
$synchronizationResult = $session.SynchronizeDirectories(
[WinSCP.SynchronizationMode]::Local, $_.Destination, $_.Source, $False)
$synchronizationResult.Check()
foreach ($download in $synchronizationResult.Downloads ) {
Write-Host "File $($download.FileName) downloaded" -ForegroundColor Green
$SMTPBody +=
"`n Files: $($download.FileName -join ', ') `n" +
"Current Location: $($_.Destination)`n"
Send-MailMessage #SMTPMessage -Body $SMTPBody
}
$transferResult =
$session.GetFiles($_.Source, $_.Destination, $False, $transferOptions)
#Find the latest downloaded file
$latestTransfer =
$transferResult.Transfers |
Sort-Object -Property #{ Expression = { (Get-Item $_.Destination).LastWriteTime }
} -Descending |Select-Object -First 1
}
if ($latestTransfer -eq $Null) {
Write-Host "No files found."
$SMTPBody += "There are no new files at the moment"
}
else
{
$lastTimestamp = (Get-Item $latestTransfer.Destination).LastWriteTime
Write-Host (
"Downloaded $($transferResult.Transfers.Count) files, " +
"latest being $($latestTransfer.FileName) with timestamp $lastTimestamp.")
$SMTPBody += "file : $($latestTransfer)"
}
Write-Host "Waiting..."
Start-Sleep -Seconds 5
}
finally
{
Send-MailMessage #SMTPMessage -Body $SMTPBody
# Disconnect, clean up
$session.Dispose()
}
}
catch
{
Write-Host "Error: $($_.Exception.Message)"
}
I believe your code has more problems than you think.
Your combination of SynchronizeDirectories and GetFiles is suspicious. You first download only the new files by SynchronizeDirectories and then you download all files by GetFiles. I do not think you want that.
On any error the .Check call will throw and you will not collect the error into your report.
You keep sending partial reports by Send-MailMessage in the foreach loop
This is my take on your problem, hoping I've understood correctly what you want to implement:
$SMTPBody = ""
Import-Csv -Path "FILESOURCE.csv" -ErrorAction Stop | foreach {
Write-Host "$($_.Source) => $($_.Destination)"
$SMTPBody += "$($_.Source) => $($_.Destination)`n"
$synchronizationResult =
$session.SynchronizeDirectories(
[WinSCP.SynchronizationMode]::Local, $_.Destination, $_.Source, $False)
$downloaded = #()
$failed = #()
$latestName = $Null
$latest = $Null
foreach ($download in $synchronizationResult.Downloads)
{
if ($download.Error -eq $Null)
{
Write-Host "File $($download.FileName) downloaded" -ForegroundColor Green
$downloaded += $download.FileName
$ts = (Get-Item $download.Destination).LastWriteTime
if ($ts -gt $latest)
{
$latestName = $download.FileName;
$latest = $ts
}
}
else
{
Write-Host "File $($download.FileName) download failed" -ForegroundColor Red
$failed += $download.FileName
}
}
if ($downloaded.Count -eq 0)
{
$SMTPBody += "No new files were downloaded`n"
}
else
{
$SMTPBody +=
"Downloaded $($downloaded.Count) files:`n" +
($downloaded -join ", ") + "`n" +
"latest being $($latestName) with timestamp $latest.`n"
}
if ($failed.Count -gt 0)
{
$SMTPBody +=
"Failed to download $($failed.Count) files:`n" +
($failed -join ", ") + "`n"
}
$SMTPBody += "`n"
}
It will give you a report like:
/source1 => C:\dest1`
Downloaded 3 files:
/source1/aaa.txt, /source1/bbb.txt, /source1/ccc.txt
latest being /source1/ccc.txt with timestamp 01/29/2020 07:49:07.
/source2 => C:\dest2
Downloaded 1 files:
/source2/aaa.txt
latest being /source2/aaa.txt with timestamp 01/29/2020 07:22:37.
Failed to download 1 files:
/source2/bbb.txt
To check and make sure the csv file exists before you process the entire thing, you can use the Test-Path,
...
if (!(Test-Path D:\FileSource.csv)) {
Write-Output "No File Found"
$SMTPBody += "There are no new files at the moment"
return; # Dont run. file not found. Exit out.
}
Import-Csv -Path "D:\FILESOURCE.csv" -ErrorAction Stop | foreach {
...
and for the Body error you are getting, it is coming from the finally loop because there are cases where $SMTPBody would be null. This will no longer be an issue because $SMTPBody will have some text when file is not found at the beginning.
Even though you are using return in the if statement to check if the file exists, finally will always get executed. Since we updated $smtpbody, your Send-MailMessage will no longer error out.
Update
If you want to check if the file you are downloading already exists, you can use the if statement like this,
foreach ($download in $synchronizationResult.Downloads ) {
if (!(Test-Path Join-Path D: $download.FileName) {
$SMTPBody += "File $($download.Filename) already exists, skipping."
continue # will go to the next download...
}
Write-Host "File $($download.FileName) downloaded" -ForegroundColor Green
...
If you do get the error regarding body, thats mostly because your script came across an exception and was sent straight over to finally statement. Finally statement sends the email with empty body because it was never set (due to exception). I would recommend using the debugger (step through) and see which step causes the exception and look into adding steps to make sure script doesnt fail.
I have created a script which works fine if a list is not threshold. Provided below is my script:
Foreach($ListItem in $List){
#Get workflows and its properties and traversing each item
$WorkflowInstance = Get-PnPWorkflowInstance -List $ListName -ListItem $ListItem.Id
$ListItem
ForEach($Item in $WorkflowInstance)
{
If($Item.FaultInfo){
$Fault = $(If ($Item.FaultInfo.IndexOf("`n") -gt 0) {$Item.FaultInfo.Substring(0, $Item.FaultInfo.IndexOf("`n"))} Else {$Item.FaultInfo})
}
Else{
$Fault = {}
}
$URL = "$($Item.Context.Url)_layouts/15/wrkstat.aspx?List={$($ListItem["GUID"])}&WorkflowInstanceName=$($Item.Id)"
Add-Content -Path $Path -Value "$($ListItem["Title"]),$($Item.Status),$($Item.UserStatus),$($Item.InstanceCreated),$($Item.LastUpdated),$URL,$($Fault)"
Write-Host -ForegroundColor Green "$($ListItem["Title"])/$($Item.UserStatus) --- Completed"
}
}
I am not encountering any error but I don't receive any workflows either. I did some debugging and it seems that on $WorkflowInstance, $ListName has a value and so did $ListItem.Id, but $WorkflowInstance itself is null. I would like to seek help as I've been struggling for this piece of code for days.
This is an attempt to get a specific answer to a possible solution to sqljob, on failed step, retry previous step, but not indefinitely
I have a job, which transfer a file using
powershell -command "Invoke-WebRequest https://someserver.dom/fetch.php -OutFile c:/tmp/data.xml"
Sometime the data from upstream is not proper XML. I just want to "grep" for the final line in the file, if it is not the expected </records> I want the step to fail, so the step is retried a few times before it hard-fails.
The filesize could also be a error-criterium. A failed transfer is a few lines, a successfil is megabytes.
So basically i made a function just for this. Set the amount of attempts and the script to run.
function TryAgain($Attempts = 1, $Scriptblock){
while($Attempts -gt 0){
try{
Invoke-Command -ScriptBlock $Scriptblock
Break
}catch{
$_.Exception
$Attempts -= 1
}
}
}
In this case we want to find out if it is valid XML so we are going to get the .RawContent of the Invoke-WebRequest and test against $(new-object System.Xml.XmlDocument).LoadXml(). If it fails throw a error, if it passes then out the raw content to a file. Errors will not be saved to the file only valid XML
$Site = "https://someserver.dom/fetch.php"
$OutFile = "c:/test/data.xml"
(Invoke-WebRequest $Site).RawContent | %{
try{
$(new-object System.Xml.XmlDocument).LoadXml($_)
$_ | out-file $OutFile
}catch{
throw "Bad XML"
}
}
Now we combine into a final product
$Site = "https://someserver.dom/fetch.php"
$OutFile = "c:/test/data.xml"
function TryAgain($Attempts = 1, $Scriptblock){
while($Attempts -gt 0){
try{
Invoke-Command -ScriptBlock $Scriptblock
Break
}catch{
$_.Exception
$Attempts -= 1
}
}
}
TryAgain -Attempts 3 -Scriptblock {
(Invoke-WebRequest $Site).RawContent | %{
try{
$(new-object System.Xml.XmlDocument).LoadXml($_)
$_ | out-file $OutFile
}catch{
throw "Bad XML"
}
}
}
since you dont need the retries and all the glam here is it in basic
(Invoke-WebRequest https://someserver.dom/fetch.php).RawContent | %{
try{
(new-object System.Xml.XmlDocument).LoadXml($_)
$_ | out-file C:\test\test.xml
}catch{}
}
I adapted an AD replication powershell script I found online to include the code below:
function ExitWithCode {
param
(
$exitcode
)
$host.SetShouldExit($exitcode)
exit
}
function Write-Log {
<#
.SYNOPSIS
Write-Log writes a message to a logfile
.DESCRIPTION
The Write-Log function is designed to add logging capability to other scripts.
In addition to writing output and/or verbose you can write to a log file for
later debugging.
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[Alias('LogContent')]
[string]$Message,
[Parameter(Mandatory = $false)]
[ValidateSet("Error", "Info", "Status")]
[string]$Level = "Info",
[Parameter(Mandatory = $false)]
[Alias('LogPath')]
[string]$Path = 'C:\dataloop\ADHealthCheck.log'
)
BEGIN {
[string]$FormattedDate = Get-Date -Format "dd-MM-yyyy HH:mm"
If (-NOT (Test-Path $path)) {
Write-Verbose "Creating $Path"
[System.IO.FileInfo]$LogFile = New-Item $Path -Force -ItemType file
}
}
PROCESS {
[string]$LogLine = "$FormattedDate - $Level - $message"
$LogLine | Out-File -FilePath $Path -Append
Switch ($Level) {
"Info" {Write-Verbose $LogLine}
"Status" {Write-Output $LogLine}
"Error" {Write-Error $LogLine}
}
}
END {}
}
function Get-ADHealthCheck {
[CmdletBinding()]
param()
BEGIN {
Write-Log "Beginning the AD Health Check..."
}
PROCESS {
$DCs = Get-ADDomainController -Filter * |sort name
Write-Log "$($DCs.Count) Domain Controllers found" -level Info
$results = #()
ForEach ($DC in $DCs) {
Write-Log "Getting replication metadata for $($DC.HostName)" -level Status
$ReplStatuses = Get-ADReplicationPartnerMetadata -target $DC.HostName -PartnerType Both -ErrorAction SilentlyContinue
If ($ReplStatuses) {
Write-Log "$($ReplStatuses.Count) replication links found for $($DC.HostName)" -level Info
ForEach ($ReplStatus in $ReplStatuses) {
$Partner = $ReplStatus.Partner.Split(",")[1].Replace("CN=","")
$results += [pscustomobject] #{
'Source DC' = $DC.HostName.ToUpper()
'Partner DC' = (Get-ADComputer $Partner).DNSHostName.ToUpper()
'Direction' = $ReplStatus.PartnerType
'Type' = $ReplStatus.IntersiteTransportType
'Last Attempt' = $ReplStatus.LastReplicationAttempt
'Last Success' = $ReplStatus.LastReplicationSuccess
'Last Result' = $ReplStatus.LastReplicationResult
}
}
}
Else {
Write-Log "Unable to get replication status for $($DC.HostName)" -level Error
$results += [pscustomobject] #{
'Source DC' = $DC.HostName.ToUpper()
'Partner DC' = "N/A"
Direction = "N/A"
Type = "N/A"
'Last Attempt' = "N/A"
'Last Success' = "N/A"
'Last Result' = "N/A"
}
}
}
ForEach ($result in $results) {
If ("$($results.'Last Result')" -eq "0") {
Write-Log "There were no replication issues found" -Level Info
ExitWithCode -exitcode 0
}
Else {
Write-Log "These domain controllers have replication errors. Please review them..." -Level Error
$error = $results | where {"$($_.'Last Result')" -ne "0"} | select 'Source DC','Partner DC','Direction' | ft -AutoSize
Write-Log $error -Level Error
ExitWithCode -exitcode 2
}
}
}
}
Get-ADHealthCheck
Basically the only issue I'm having now is the last if/else block. I need it to loop through every entry in the $results hash table and if the "Last Result" key only contains "0", then exit with code 0. If it finds any other values, it should output the source, partner, and direction value(s) fromt he hash table.
Currently, if it encounters an issue, it jumps to the else block, outputs the information requested and then runs the ExitWithCode function which eventually kills the script so anything that comes after the error is not checked.
I've been looking at this too long and have been unsuccessful so I'm throwing it out to there since it may just be something simple I'm missing.
Look at your for loop variables
ForEach ($result in $results) {
For each single $result in the $results. In the following if statement you should be looking at one $result but instead you are doing a comparison against all results. Your subexpression syntax here is also not required.
If ("$($results.'Last Result')" -eq "0")
Note that this is perfectly valid code but it will not get you the results you expect. It will return all 'last result's that are 0. So if even one in the whole collection is 0 the true condition will always fire.
So lets just make some minor changes and use the singular $result
If ($result.'Last Result' -eq "0")
That should get you the result you are looking for. I notice that you are looking for equality with the string 0. That will not be an issue here since the LHS sets the type for the comparison. Have a look at this other post to get a better understanding of what PowerShell does here.
Why is $false -eq "" true?
If your last result was the int 0 this would be true
0 -eq "0"
Your logic appears to be flawed as you have mentioned in comments on how to handle overall success and individual failures. Instead of looping through the results like you were I think we need to check the collection as a whole and loop only if errors are encountered.
# Lets check if any of the results contain failure
$failedChecks = $results | Where-object{$_.'Last Result' -ne 0}
# Evaluate $failedChecks as a boolean. If there are no failed checks Else can be assumed that everything is fine.
if($failedChecks){
Write-Log "These domain controllers have replication errors. Please review them..." -Level Error
$error = $failedChecks | select 'Source DC','Partner DC','Direction' | ft -AutoSize | Out-String
Write-Log $error -Level Error
ExitWithCode -exitcode 2
} else {
Write-Log "There were no replication issues found" -Level Info
ExitWithCode -exitcode 0
}