Powershell script to retry move-item until success is not working - powershell

I wrote the following loop to sleep 5 seconds if the attempt to rename the file fails, however, when the event log reports failure ($? is false) it isn't retrying, it just moves on to the next block of code:
DO
{
Move-Item -Path $old_log_name -Destination $new_log_name
if($?){
Write-EventLog -LogName Application -Source my_application -EventId 4 -Message "Successfully moved $old_log_name to $new_log_name"
}
else{
Write-EventLog -LogName Application -Source my_application -EventId 4 -Message "Failed to move $old_log_name to $new_log_name : $LastExitCode sleeping 5 secconds"
Start-Sleep -Seconds 5
}
} Until ($?)
Am I missing something here??? Also on the same topic, $LastExitCode is showing up as blank. Any ideas why?

If I understand you correctly, what you're trying to do is the following:
Try to move the item and set a new name, the names are stored in the variables $old_log_name and $new_log_name.
If it fails to rename the item, it should write a fail message to the EventLog and stay in the loop until the item has been moved and renamed.
If it succeeds with moving and renaming the item, it should write a success message to the EventLog.
If the above is the case, you can easily achieve this by using a -not (!()) statement in correlation with the Do/Until-loop.
$old_log_name = 'C:\temp\test.txt'
$new_log_name = 'C:\temp\temp.txt'
Do {
if (!(Move-Item -Path $old_log_name -Destination $new_log_name -ErrorAction SilentlyContinue)) {
Write-EventLog -LogName Application -Source my_application -EventId 4 -Message "Failed to move $old_log_name to $new_log_name : sleeping 5 secconds"
Start-Sleep -Seconds 1
}
}
Until ((Test-Path $new_log_name) -eq $true)
Write-EventLog -LogName Application -Source my_application -EventId 4 -Message "Successfully moved $old_log_name to $new_log_name"
The above script will try to move and rename the item until the statement in the until-loop becomes $true. In other words, when the "Test-Path" returns a $true value the loop will have been completed.
The reason as to why: $LastExitCode doesn't work is because it only works with Win32 applications and not with PowerShell commands. A better solution for that in this case would be to add a $true or $false statement to a variable if the statement is $true or $false and then append an errorcode.

Related

Powershell script does not continue on write-eventlog

I have the following block in script that checks a log file and if it finds a specific line, should continue.
$keywords=Get-Content "C:\Users\user\desktop\keywords.txt"
Get-Content "C:\Users\user\desktop\some.log" -tail 1 -wait |
ForEach-object {
foreach($word in $keywords) {
if($_ -match "$word") {
Write-EventLog -LogName Application -EventID 2001 -EntryType Information -Source serviceCheck -Message "[SUCCESS] The service has been initialized"
Write-Host "[SUCCESS] The service has been initialized"
}
}
} | Select -First 1
This logs the event but never continues with the rest of the script.
If I put some other command in if($_ -match "$word") {get-date} for example or anything else, it works and continues to the next command.
How should this be made to write in event viewer and continue?
You need to output something for the Select -First 1 statement to react to:
$keywords = Get-Content "C:\Users\user\desktop\keywords.txt"
Get-Content "C:\Users\user\desktop\some.log" -tail 1 -wait |ForEach-object {
foreach ($word in $keywords) {
if ($_ -match "$word") {
Write-EventLog -LogName Application -EventID 2001 -EntryType Information -Source serviceCheck -Message "[SUCCESS] The service has been initialized"
Write-Host "[SUCCESS] The service has been initialized"
"literally any value will do!"
}
}
} | Select -First 1 |Out-Null

Using Powershell to backup remote computers event logs

I'm very new to PowerShell and my end goal is to backup event logs on remote servers to a fileshare on the network. I was able to get my script working locally on a single server, backing up the servers event logs to a folder. Now, I'm trying to run this script from "server A" and backup event logs on "Server B" and "Server C". I was hoping I can accomplish this via simply creating a new ps session for each server and copy pasting my code. This of course wasn't the case. I'm guessing I might have to go through and add -Computer parameters to somethings?
I got the foundational code from this website
Was hoping a PS guru could point out my failures, here's my code:
$Servers = 'ServerB'
$ArchiveServer = 'ServerA' #For Testing
foreach ($Server in $Servers){
Enter-PSSession -ComputerName $Server -Credential mycred
$RemoteArchive = "\\" + $ArchiveServer +"\c$\z-TestScript-lastname"
$LocalArchive = "C:\z-TestScript"
$ArchiveComp = "\$Server"
$ArchiveFolder = "\Archive-" + (Get-Date -Format "yyyy-MM-dd") + "test14" ### Added test to get past testpath check
$RemoteLogFolder = "\\" + $Server + "\c$\Windows\System32\winevt\Logs"
$LogFolder = "C:\Windows\System32\winevt\Logs\*"
$RemoteCompressedPath = -join("$RemoteArchive","$ArchiveComp","$ArchiveFolder",".zip")
$CompressedPath = -join("$ArchiveLoc","$ArchiveComp","$ArchiveFolder",".zip")
$RemoteArchivePath = -join("$RemoteArchive","$ArchiveComp","$ArchiveFolder")
$ArchivePath = -join("$ArchiveLoc","$ArchiveComp","$ArchiveFolder")
$winlogs = 'application', 'security', 'system', 'setup', 'hardwareevents', 'internet explorer', 'key management service', 'oalerts', 'Parameters', 'oneapp_IGCC', 'windows powershell'
# Checking paths
If (!(Test-Path $RemoteArchive)) {
Write-Host
Write-Host "Archive folder $RemoteArchive does not exist, aborting ..." -ForegroundColor Red
Exit
}
If ((Test-Path $RemoteArchivePath)) {
Write-Host
Write-Host "Archive path $RemoteArchivePath exists, aborting ..." -ForegroundColor Red
Exit
}
If (!(Test-Path $RemoteArchivePath)) {
Write-Host
Write-Host "Creating Archive folder $RemoteArchivePath ..." -ForegroundColor Red
New-Item -Path $RemoteArchivePath -type directory -Force
}
# For all newer event logs, archive.
foreach ($winlog in $winlogs) {
# Configure environment
$sysName = $Server
$eventName = "$winlog Event Log Monitoring"
# Add event source to log if necessary
If (-NOT ([System.Diagnostics.EventLog]::SourceExists($eventName))) {
New-EventLog -ComputerName $Server -LogName $winlog -Source $eventName
}
# Check the log
if ( $winlog -ne 'Setup'){
$Log = Get-WmiObject Win32_NTEventLogFile | Where-Object {$_.logfilename -eq "$winlog"}
# Archive the log
$ArchiveFile = $ArchivePath + "\$winlog-" + (Get-Date -Format "yyyy-MM-dd#HHmm") + ".evt"
$EventMessage = "The $winlog event log will now be backed up."
$Results = ($Log.BackupEventlog($ArchiveFile)).ReturnValue
If ($Results -eq 0) {
# Successful backup of the event log
$Results = ($Log.ClearEventlog()).ReturnValue
$EventMessage += "The $winlog event log was successfully archived to $ArchiveFile and cleared."
Write-Host $EventMessage
Write-EventLog -LogName $winlog -Source $eventName -EventId 11 -EntryType Information -Message $eventMessage -Category 0
}
Else {
$EventMessage += "The #winlog event log could not be archived to $ArchiveFile and was not cleared. Review and resolve security event log issues on $sysName ASAP!"
Write-Host $EventMessage
Write-EventLog -LogName $winlog -Source $eventName -EventId 11 -EntryType Error -Message $eventMessage -Category 0
}
# Close the log
$Log.Dispose()
}
# For older archives like setup, archive.
Else {
$Log = Get-winevent -Listlog Setup | select Logname, Logfilepath | ForEach-Object -Process {
$name = $_.Logname
$safename = $name.Replace("/","-")
$logpath = $_.Logfilepath
$path = $ArchivePath + "\Setup" + (Get-Date -Format "yyyy-MM-dd#HHmm") + ".evt"
wevtutil.exe EPL $safename $path
Write-Host "Copying Setup log to: $path"
}
}
}
#Copy any windows archived logs over, then delete
Write-Host "Copying Archived Logs..."
Get-ChildItem -Path $LogFolder -Include Archive* -Recurse | Copy-Item -Destination $ArchivePath
Get-ChildItem -Path $LogFolder -Include Archive* -Recurse | Remove-Item
#Compress the folder
Write-Host "Compressing $ArchivePath and moving to $CompressedPath"
Compress-Archive -Path $ArchivePath -Destination $CompressedPath
Exit-PSSession
}
Main errors I'm getting as it goes through the foreach loop for the winlogs, it seems to run it twice, once for remote pc and once for local.

keep reading from file, looking for some keywords

I have a log file that needs to keep monitored that file looking for some keywords, I created a PowerShell script and used the get-content command with the -wait flag but, get-content is just reading the first keyword, and If I remove -wait it will work fine and read all keywords, but it will exit after finished, and I want the file to keep waiting for the appended lines.
Please advise me on this.
This is my code:
$date = Get-date -Format "yyyyMMdd"
$MyError = #('error 1', 'error 2', 'error 3')
$file=$date.log"
foreach ($i in $MyError) {
Get-Content "$file" -Wait | Select-String -Pattern $i -SimpleMatch | ForEach-Object {
if ($i -like 'error 1') {
Write-EventLog -source Test -LogName Test -EventId 1001 -EntryType Error -Message " "
}
if ($i -like 'error 2') {
Write-EventLog -source Test -LogName Test -EventId 1002 -EntryType Error -Message " "
}
if ($i -like 'error 3') {
Write-EventLog -source Test -LogName Test -EventId 1003 -EntryType Error -Message " "
}
}
}
The issue with your code is that -Wait blocks the main thread, waits indefinitely until CTRL+C. Because of this, your outer foreach loops only once, so $i is always error 1. An easy fix for your problem is to loop inside the ForEach-Object, but also, using a switch -Wildcard would simplify your code:
Get-Content path\to\file.txt -Wait | ForEach-Object {
switch -Wildcard ($_) {
'*error 1*' {
# do something for error 1
}
'*error 2*' {
# do something for error 2
}
'*error 3*' {
# do something for error 3
}
}
}

Powershell: Write to a single Logfile

I have written a small function that writes into a single logfile for each server this module is installed, but somehow a few cycles to write the line are lost, sometime I get the error file already in use. I have tested this function on different version of Powershell, PS5.1, PS6, PS7.1 x86 and x64 all with the same result.
if (!(Test-Path -ErrorAction Stop -Path (Join-path -Path $ScriptLogPath -ChildPath "$FQDN.log"))) {
New-Item -Path $ScriptLogPath -Name "$FQDN.log" -Force -ItemType File -ErrorAction Stop
}
$CreatedNew = $false
$Global:MTX = New-Object -TypeName System.Threading.Mutex($true, 'Global\LogFileMutex', [ref]$CreatedNew)
try {
if (-not $CreatedNew) {
$Global:MTX.WaitOne(10000) | Out-Null
}
}
catch [System.Threading.AbandonedMutexException] {
if (!(Test-Path -Path 'HKLM:\System\CurrentControlSet\services\eventlog\Application\LogWriter')) {
New-EventLog -LogName Application -Source LogWriter
}
Write-EventLog -LogName "Application" -Source "LogWriter" -EntryType Error -Message $_.Exception -EventId 1000
}
($Date + $InstanceId + $Severity + $ScriptLineNumber + $ScriptName + $Message) | Out-File -Append -FilePath (Join-path -Path $ScriptLogPath -ChildPath "$FQDN.log")
}
catch {
if (!(Test-Path -Path 'HKLM:\System\CurrentControlSet\services\eventlog\Application\LogWriter')) {
New-EventLog -LogName Application -Source LogWriter
}
Write-EventLog -LogName "Application" -Source "LogWriter" -EntryType Error -Message $_.Exception -EventId 1000
}
finally {
if ($Null -ne $Global:MTX) {
[void]$Global:MTX.ReleaseMutex()
[void]$Global:MTX.Dispose()
}
}
That is the block of code that will append the formatted string into the file.
So fare I have tried different approaches to handle the Mutex, made them globally and not. Used different approaches to append the string to the file, like Add-Content.
To try out the function I use this little script, it should write 150 lines, but end up with about 130 and no single error.
for ($num1 = 1 ; $num1 -le 5 ; $num1++) {
Start-Job {
for ($num = 1 ; $num -le 10 ; $num++) {
Write-Log -Severity 'INFO' -Message "Starting Job $num"
Start-Job -ScriptBlock {
try {
Start-Sleep -Milliseconds (Get-Random -Maximum 10)
Write-Log -Severity 'INFO' -Message "Starting"
Start-Sleep -Milliseconds (Get-Random -Maximum 10)
Write-Log -Severity 'INFO' -Message "DONE"
}
catch {
New-Item -Path "C:\Users\User\Desktop" -Name (Get-Date -Format "dd/MM/yyyy HH:mm:ss") -ItemType File
}
}
}
}
Since every server is writing to the same logfile, i dont think that you can override "error file already in use"
Iam thinking that maby you should think over the concept and run from a server where you are monitoring by invoke-commmand ?

Find old computers in AD is not accurate?

Playing around with the Get-EventLog command. The first 3 commands work as expected.
Get-EventLog Application -Newest 1000 | Select Message
Get-EventLog System -Newest 1000 | Select Message
Get-EventLog Security -Newest 1000 | Select Message
But this does not work
Get-EventLog Setup -Newest 1000 | Select Message
and this does not work
Get-EventLog setup
How come? There are WSUS errors in the Setup that we'd like to capture.
Sorry this is a bit long but I'm kind of fond of try/catch statements and over-communication.
#requires -Version 2.0
function RemoteEventLog([Parameter(Mandatory=$true)]$LogName, $MaxEvents,[Parameter(Mandatory = $true)]$computers, $LogPath)
{
<#
.SYNOPSIS
Gather remote event logs by log name by computer names.
.DESCRIPTION
Specifiy a log name to narrow down the search. Provide as many computernames as needed.
.PARAMETER maxevents
-maxevents is all about how many events.
.PARAMETER computers
The array or list of comma separated computer names to run the script through.
.PARAMETER LogPath
-LogPath will let you decide the parent folder of the location to store the logs by computer name.
.EXAMPLE
RemoteEventLog -logname Application -maxevents 1000 -computers ('host1','host2','host3')
This will loop through the computers and bring back the log for each computer.
#>
$computers = $computers -split (',')
try
{
$testLogPath = Test-Path $LogPath
}
catch
{
"Error was $_"
$line0 = $_.InvocationInfo.ScriptLineNumber
"Error was in Line $line0"
}
if(!($testLogPath))
{
try
{
New-Item -Path $LogPath -ItemType Directory -ErrorAction:Stop
}
catch
{
"Error was $_"
$line1 = $_.InvocationInfo.ScriptLineNumber
"Error was in Line $line1"
}
}
foreach($computer in $computers)
{
try
{
$log = Get-WinEvent -LogName $logName -MaxEvents $maxevents -ComputerName $computer -ErrorAction:Stop
}
catch
{
"Error was $_"
$line2 = $_.InvocationInfo.ScriptLineNumber
"Error was in Line $line2"
}
try
{
New-Item -Path $LogPath -Name ("$computer.evt") -Value $log -Force
$log | Out-File -FilePath $LogPath
}
catch
{
"Error was $_"
$line3 = $_.InvocationInfo.ScriptLineNumber
('Error was in Line {0}' -f $line3)
}
}
}
RemoteEventLog -logname Application -MaxEvents 100 -computers 'localhost,computer2,computer3' -LogPath D:\Desktop\logs