I have the following script where, when the if(-not) condition is met, the log file is being generated but not error is being written in the log file.
I have very basic understanding of scripting languages and as a result cannot understand why nothing is being logged. Help is appreciated.
$resulterror = $error[0].exception.message
$BckDate = Get-Date -Format "ddMMyyyy"
$BckFolder = "<path>"+$BckDate
$log = "<path>"+$BckDate+".log"'
Copy-Item -Path $BckFolder -Destination <drive-letter> -ErrorAction silentlyContinue -recurse
if(-not $?) {$resulterror >> $log ; invoke-expression -Command "<path-of-ps-script-to-run>"}
else {"Backup "+$BckDate+" done successfully" >> $log}
The real paths have been hidden with the "path" in <> explaining that it is a directory path.
The path of the script triggered when the if(-not) condition is met is replaced with path-of-ps-script-to-run in <>. The <> are just brackets for the comments so don't confuse them with actual code symbols.
I would use the -ErrorVariable advanced parameter to store the error message in that variable and access it:
Copy-Item -Path $BckFolder -Destination <drive-letter> -ErrorAction silentlyContinue -recurse -ErrorVariable copyError
if($copyError)
{
$resulterror | Out-File -FilePath $log -Append
invoke-expression -Command "<path-of-ps-script-to-run>"
}
else
{
"Backup "+$BckDate+" done successfully" | Out-File -FilePath $log -Append
}
Your variable $resulterror is empty since you saved it at the very begining of the script and there were no errors at that time. Try to use explicit $error[0].exception.message instead of $resulterror on IF condition.
$BckDate = Get-Date -Format "ddMMyyyy"
$BckFolder = "<path>"+$BckDate
$log = "<path>"+$BckDate+".log"
Copy-Item -Path $BckFolder -Destination <drive-letter> -ErrorAction silentlyContinue -recurse
if(-not $?) {$error[0].exception.message >> $log ; invoke-expression -Command "<path-of-ps-script-to-run>"}
else {"Backup "+$BckDate+" done successfully" >> $log}
Related
I'm trying to get powershell to write information about the execution of an operation to a log file. There is a folder with logs, it is necessary that the script delete files older than a certain number of days from the folder (implemented) and record information about the deletion of files or the absence of files in the folder (in case of an error when executing the script). Now the script writes information to the log about the absence of files, then if they are. Tell me what to do wrong? The text is as follows:
if ($error) {"No files found" *>> "D\TEST\Log $(gat-date -f dd-MM-yyy).txt"}
else {"Files deleted" *>> "D\TEST\Log $(gat-date -f dd-MM-yyy).txt"}
In principle $error is a automatic variable containg all errors occured within the current session. From my point of view its a better approach to use try/catch to handle errors. By doing so you can specify the error message to write to the logfile that matches the error, instead of writing the same error message for any kind of errors e.g.:
try {
#do someting
"Did something successfully" | set-content -path $logfilePath -Append -Force -Confirm:$false
}
Catch {
#gets executed if a terminating error occured
write-error "Failed to do something, Exception: $_"
"Failed to do something, Exception: $_" | set-content -path $logfilePath -Append
}
But back to your example, you could do:
$date = get-date -Format dd-MM-yy
$logFilePath = "D\TEST\Log_$date.txt"
If ($error){
"No files found" | set-content -path $logfilePath -Append -Force -Confirm:$false
}
Else {
"Files deleted" | set-content -path $logfilePath -Append -Force -Confirm:$false
}
ok based on the comment you could go this route:
$date14daysBack = (get-date).AddDays(-14)
$date = Get-Date -Format dd-MM-yyyy
$LogfilePath = "D:\TEST\Log_$date.txt"
try {
#try to map drive, if something goes wrong stop processing. You do not have to escape the $ if you use single quotes
New-PSDrive -Name "E" -PSProvider "FileSystem" -Root '\\Computer\D$\TEST' -Persist -ErrorAction:Stop
}
Catch {
"Failed to map drive '\\Computer\D$\TEST' - Exception $_" | Set-Content -Path $logfilePath -Append -Force
throw $_
}
try {
#I replaced forfiles and the delete operation with the powershell equivalent
$files2Remove = get-childitem -path E:\ -recurse | ?{$_.LastWriteTime -ge $date14daysBack}
$null = remove-item -Confirm:$false -Force -Path $files2remove.FullName -ErrorAction:stop
}
Catch {
"Failed to delete files: $($files2remove.FullName -join ',') - Exception: $_" | Set-Content -Path $logfilePath -Append -Force
}
#If files were found
If ($files2Remove){
"Files deleted, count: $($files2remove.count)" | Set-Content -Path $logfilePath -Append -Force
}
Else {
"No files found" | Set-Content -Path $logfilePath -Append -Force
}
Currently the code does not filter for '.' like in your sample: /m . - do I understand this correctly the filename is only a dot, nothing else?
The script has earned in the following form:
#cleaning up errors
$Error.Clear()
#days storage
$int = 11
#connecting a network drive
New-PSDrive -Name "E" -PSProvider "FileSystem" -Root "\\Computer\D`$\TEST" -Persist
#deleting files
FORFILES /p E:\ /s /m *.* /d -$int /c "CMD /c del /Q #FILE"
#disabling a network drive
Remove-PSDrive E
#recording information in the log
$date = Get-Date -Format dd-MM-yyyy
$LogfilePath = "D:\TEST\Log_$date.txt"
(Get-Date) >> $LogfilePath
if ($Error){"No files found" | Add-content -path $LogfilePath -Force -Confirm:$false}
Else {"Files deleted" | Add-Content -path $LogfilePath -Force -Confirm:$false}
I am trying to schedule a PowerShell Core 7.2 script to run on Windows Server 2012R2.
The script runs manually, without any errors, from the server and the Task Scheduler runs the task. In the History, I can see Task Completed
The issue is that the script is not executed. It is supposed to move the files and files are not moving which means the script was not executed.
Settings of the Task Scheduler that are selected are as follows:
General - Run whether user is logged on or not, Run with the highest privileges.
Actions -> Action Start a Program
Actions -> Program/Script "C:\Program Files\PowerShell\7\pwsh.exe" (location of pwsh.exe)
Actions -> Add Arguments -Executionpolicy Bypass -File "R:\Public\IT\Vantage_Utilities\CNC_Scripts\File Transfer\Fastems\CNC_File_Transfer_Fastems.ps1"
Location -> Name of the local machine
I am not really sure what is going wrong here.
EDIT
I am thinking there is an issue with the script. Because there is another script set up to be executed with PS Core and Task Scheduler. I am going to post the script here. It is a simple batch file that moves all the contents of one folder from one server to another. I achieve this in two functions. Function MoveFiles moves all the contents of the parent folder(excluding the subfolder called "Mazak"). The second function,Function MoveMazakFiles function moves the contents of "Mazak" only. ( I am completely aware I could have done this using fewer lines of code but that is not the point here)
Code:
$logPath = "\\MMS25163S1\Public\IT\Vantage_Utilities\CNC_Scripts\File Transfer\Fastems\Log.txt"
$trancriptPath = "\\MMS25163S1\Public\IT\Vantage_Utilities\CNC_Scripts\File Transfer\Fastems\LogTranscript.txt"
$getDate = Get-Date -Format "dddd MM/dd/yyyy HH:mm "
$counter = 0
$mazakCounter = 0
Start-Transcript -Path $trancriptPath -Append
Add-Content -Path $logPath -Value ("LOG CREATED $getDate") -PassThru
#Sources
$srcMca = "\\MMS25163S1\Public\NcLib\FromNC\*"
$srcMcaNameChg ="\\MMS25163S1\Public\NcLib\FromNC"
$srcMazak= "\\MMS25163S1\Public\NcLib\FromNC\Mazak\*"
$srcMcaNameChgMazak = "\\MMS25163S1\Public\NcLib\FromNC\Mazak"
#Destination
$destMca = "\\Sidney2\MfgLib\RevisedPrograms\MC-A"
#Time with milliseconds
$time = (Get-Date -Format hh-mm-fff-tt).ToString()
Function MoveFiles{
Param(
[string]$src,
[string]$dest,
[string]$srcNameChange
)
Get-Item -Path $src -Exclude *Mazak* -ErrorAction SilentlyContinue | ForEach-Object{
$counter++
$fileName = $_.BaseName
$fileNameExt = $_.Name
Write-host $fileName -ForegroundColor Green
Rename-Item -Path "$srcMcaNameChg\$fileNameExt" -NewName ($fileName+"_"+"(Time-$time)"+$_.Extension);
Add-Content -Path $logPath -Value ("Name changed: Time stamp added to $fileName ") -PassThru
}
Move-Item -Path $src -Exclude *Mazak* -Destination $dest -Force
Add-Content -Path $logPath -Value ("$counter file(s) moved to $dest") -PassThru
}
MoveFiles -src $srcMca -dest $destMca -srcNameChange $srcMcaNameChg
Function MoveMazakFiles{
Param(
[string]$srcMazak,
[string]$dest,
[string]$srcNameChange
)
Get-ChildItem $srcMazak -Recurse -ErrorAction SilentlyContinue | ForEach-Object{
$mazakCounter++
$fileName = $_.BaseName
$fileNameExt = $_.Name
Write-host $fileName -ForegroundColor Green
Rename-Item -Path "$srcMcaNameChgMazak\$fileNameExt" -NewName ($fileName+"_"+"(Time-$time)"+$_.Extension);
}
Move-Item -Path $srcMazak -Destination $dest -Force
Add-Content -Path $logPath -Value ("$mazakCounter file(s) from Mazak folder moved to $dest") -PassThru
}
MoveMazakFiles -srcMazak $srcMazak -dest $destMca -srcNameChange $srcMcaNameChg
Stop-Transcript
When setting the scheduled task, under Action -> Start a Program -> Program/Script. Call powershell and pass the script as the parameter
Like
powershell -File "R:\Public\IT\Vantage_Utilities\CNC_Scripts\File Transfer\Fastems\CNC_File_Transfer_Fastems.ps1"
I'm using this code to delete files older than 30 days
Function Remove_FilesCreatedBeforeDate {
$Path = "\\servername\path"
$Date = (Get-Date).AddDays(-30)
$ValidPath = Test-Path $Path -IsValid
If ($ValidPath -eq $True) {
"Path is OK and Cleanup is now running"
Get-ChildItem -Path $path -Recurse | Where-Object { $_.LastWriteTime -lt $Date } | Remove-Item -Recurse -force -Verbose
}
Else {
"Path is not a ValidPath"
}
}
Remove_FilesCreatedBeforeDate
Now I want to log which files were deleted, and also whether there was an error or the path isn't valid. Can anyone help me here?
//EDIT
Im Now using this Code (Thanks to Efie for helping)
[Cmdletbinding()]
param(
[Parameter()]$LogPath = 'C:\Admin\scripts\Clean_Folder\Log\log.txt',
[Parameter(ValueFromPipeline)]$Message
)
process {
$timeStampedMessage = "[$(Get-Date -Format 's')] $Message"
$timeStampedMessage | Out-File -FilePath $LogPath -Append
}
}
Function Remove-FilesCreatedBeforeDate {
[Cmdletbinding()]
param(
[Parameter()]$Path = '\\servername\path\',
[Parameter()]$Date = $(Get-Date).AddDays(-30)
)
process {
if(-not (Test-Path $Path -IsValid)) {
"Path $Path was invalid" | Write-MyLog
return
}
"Path $Path is OK and Cleanup is now running" | Write-MyLog
try {
Get-ChildItem -Path $Path -Recurse |
Where-Object {
$_.LastWriteTime -lt $Date
} | Remove-Item -recurse -force -verbose | Write-MyLog
}
catch {
"Remove-Item failed with message $($_.Exception.Message)" | Write-MyLog
}
}
}
Write-MyLog
Remove-FilesCreatedBeforeDate
Two files getting deleted but i just see this in my Log
[2021-07-22T16:27:53] Path \\servername\path\ is OK and Cleanup is now running
I dont see which files getting deleted sadly
A simple implementation for your example would be something like this:
Function Remove-FilesCreatedBeforeDate {
[Cmdletbinding()]
param(
[Parameter(Mandatory)]$Path = '\some\default\path',
[Parameter()]$Date = $(Get-Date).AddDays(-30)
)
process {
if(-not (Test-Path $Path -IsValid)) {
"Path $Path was invalid" | Write-MyLog
return
}
"Path $Path is OK and Cleanup is now running" | Write-MyLog
try {
Get-ChildItem -Path $Path -Recurse |
Where-Object {
$_.LastWriteTime -lt $Date
} | Remove-Item -Recurse -Force -Verbose
}
catch {
"Remove-Item failed with message $($_.Exception.Message)" | Write-MyLog
}
}
}
function Write-MyLog {
[Cmdletbinding()]
param(
[Parameter()]$LogPath = 'default\log\path\log.txt',
[Parameter(ValueFromPipeline)]$Message
)
process {
$timeStampedMessage = "[$(Get-Date -Format 's')] $Message"
$timeStampedMessage | Out-File -FilePath $LogPath -Append
}
}
Some notes:
Advanced Functions
process { }, [Cmdletbinding()], and [Parameter()] are what turn your function into an 'advanced' function. You get to use loads of built in features normally reserved for compiled cmdlets this way.
For example, you could now suppress errors with $ErrorActionPreference = 'SilentlyContinue' like you're used to doing with native Powershell cmdlets.
You can pipe your messages to your logging function by adding ValueFromPipelin to your parameter.
Those really just brush the surface of the extra capabilities you get.
Here is some information. I would recommend getting in the habit of writing them like this if you plan to use them in the future.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_advanced?view=powershell-7.1
Error Handling
I'd recommend looking into this documentation by Microsoft on error handling:
https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-exceptions?view=powershell-7.1
Naming Conventions
I would also recommend taking a look at this about PowerShell function naming conventions:
https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands?view=powershell-7
By PowerShell standards it would make more sense to name your function Remove-FilesCreatedBeforeDate with the dash separating verb-action instead of the underscore.
Logging
If you want a little more control and a few more features for logging your functions, here is some information on a tried and true solution for PowerShell using PSFramework:
https://adamtheautomator.com/powershell-logging/
Good luck! Hope some of that helps.
In Unix its Simple
find /var/log/hive -type f -mtime +30 -delete
Could Start-transcript with Try and catch be your solution here?
Start-Transcript logs everything that you do and the errors.
I tried this and this does what you want
Start-Transcript -Path "$PSScriptRoot\RemoveAccountLog.txt" -Force -Append
Get-Date -Format "yyyy-mm-dd HH:MM"
Try
{ # Start Try
$Path = "\\servername\path"
$Date = (Get-Date).AddDays(-30)
$TestPath = Test-Path -Path $Path -PathType Container
If ( $TestPath -Eq $Null )
{ # Start If
Write-Host "The $TestPath String is empty, Path is not a valid"
} # End If
Else
{ # Start Else
Write-host "Path is OK and Cleanup is now running... 0%"
$GetFiles = Get-ChildItem -Path $Path -Recurse -Force |
Where-Object { $_.LastWriteTime -lt $Date } |
Remove-Item -Recurse -force -Verbose |
Write-host "Path is OK and Cleanup is now running... 100%" -ForegroundColor Green
} # End Else
} # End Try
Catch
{ # Start Catch
Write-Warning -Message "## ERROR## "
Write-Warning -Message "## Script could not start ## "
Write-Warning $Error[0]
} # End Catch
Screenshot:
I'm working on a script to test if a file exists in a target directory, and if false, execute an application installation.
The goal is to test if the file exists and if it does, abort with a log as to why. If the file does not exist, then the application installer should be executed.
I first tried creating a dummy file using New-Item to create both the directory and the dummy file.
New-Item -ItemType Directory -Force -Path "C:\temp"
New-Item -Path "C:\temp\vistaupdated.txt" -ItemType "file" -Value "Updated"
# Test if computer is updated
$file = Get-Content "C:\temp\vistaupdated.txt"
$containsWord = $file | %{$_ -match "Updated"}
if ($containsWord -contains $false) { ###start running the install stuff
However, this errors out if the file does not exist with an Object-NotFound. I then decided to switch tactics and use Test-Path:
$file = Test-Path -Path "C:\temp\vistaupdated.txt"
if ($file -eq $false) { ###start running the install stuff
In this case, I believe $file would evaluate to False and as a result execute the installation. On execution all I get is a return of the script's path:
PS C:\users\me\desktop> $filetest = Test-Path -Path "C:\temp\vistaupdated.txt"
PS C:\users\me\desktop> $filetest
False
PS C:\users\me\desktop> C:\Users\me\Desktop\vistaupdate.ps1
The above reference to the PS1 file doesn't execute. It's only what's returned if I run the script through ISE as administrator. If I do the same with the console, then the output is blank without any action taken.
# Test if computer is updated
$file = Test-Path -Path "C:\temp\vistaupdated.txt"
if ($file -eq $False) {
#package repository
$VistaInsPath = "\\apps\shared\me\vista\6.16.0"
#package installation command
$VistaInsEXE = "VistaClient.6.16.0.896"
#package installation parameters
$VistaInsParam = "/s /v/qn"
#logging
$logFile = "\\apps\shared\me\vista\6.16.0\log\vista_install.log"
#timestamp for logging
function Get-TimeStamp {
return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)
}
#main function
function Vista-Install {
$errFlag = $false
try {
$cmdLine = "$VistaInsPath$VistaInsEXE"
Start-Process -FilePath $cmdLine -ArgumentList $VistaInsParam -Wait
} catch {
$(Get-TimeStamp) + "[" + $env:COMPUTERNAME +"]" + "[" + $env:USERNAME +"]" + "Error has occurred: " + $_.Exception.Message | Out-File $logFile -Append
$error.Clear()
$errFlag = $true
}
#if no error, notify success
if (-not $errFlag) {
$(Get-TimeStamp) + "[" + $env:COMPUTERNAME +"]" + "[" + $env:USERNAME +"]" + "Completed Successfully" | Out-File $logFile -Append
}
# Place dummy file to tag computer as updated
New-Item -ItemType Directory -Force -Path "C:\temp"
New-Item -Path "C:\temp\vistaupdated.txt" -ItemType "file" -Value "Updated"
}
} else {
$(Get-TimeStamp) + "[" + $env:COMPUTERNAME +"]" + "[" + $env:USERNAME +"]" + "Computer Already Updated. Aborting" | Out-File $logFile -Append
}
If Test-Path is False, then the installer should kick off in the first if statement. Instead the script spits back the path of the script itself and exits.
As #lit already pointed out in the comments to your question, you never invoke the function Vista-Install, so it's rather unsurprising that your code doesn't actually do anything. And you shouldn't define functions (or your $Vista* and $LogFile variables) in a nested scope anyway. The code in the else branch won't be able to find Get-TimeStamp or $LogFile with what you currently have.
Allow me to suggest some improvements:
Your logging code has a lot of redundant information. Instead of defining a function just for generating the timestamp, make a function that takes the log message as a parameter and otherwise completely encapsulates the logging.
$cmdLine = "$VistaInsPath$VistaInsEXE" will combine directory path and filename without a path separator, resulting in an incorrect path. Either put a backslash between the variables:
$cmdLine = "$VistaInsPath\$VistaInsEXE
or (better) use the Join-Path cmdlet, as #Theo suggested:
$cmdLine = Join-Path $VistaInsPath $VistaInsEXE
Put Test-Path directly in the if condition. There's no need to assign the result to a variable first.
The variable $errFlag is pointless. Just put the log statement after Start-Process. If an exception is thrown the code will go to the catch block without reaching that statement.
I assume you want the file vistaupdated.txt created only if the installation didn't throw an error, so that code should go in the try block too.
New-Item outputs an object for the item. You may want to suppress that.
The function Vista-Install doesn't make much sense either, as it would only install one specific program. Since it has very little code to begin with I would just drop it and put the code directly in the "then" branch. But if you wanted it to be a function you should name and parametrize it properly: pass program and arguments as parameters (preferably named after the parameters of Start-Process, so you can simply splat the $PSBoundParameters variable) and use a name conforming to the naming conventions:
function Install-Program {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true, Position=0)]
[string]$FilePath,
[Parameter(Mandatory=$false, Position=1)]
[string]$ArgumentList = #()
)
try {
Start-Process #PSBoundParameters
...
} catch {
...
}
}
Install-Program -FilePath (Join-Path $VistaInsPath $VistaInsEXE) -ArgumentList $VistaInsParam
Simplified code:
$VistaInsPath = '\\apps\shared\me\vista\6.16.0'
$VistaInsEXE = 'VistaClient.6.16.0.896'
$VistaInsParam = '/s', '/v', '/qn'
$logFile = '\\apps\shared\me\vista\6.16.0\log\vista_install.log'
function Write-Log {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$false)]
[string]$Message = ''
)
"[{0:MM/dd/yy HH:mm:ss}]`t[{1}\{2}]`t{3}" -f (Get-Date), $env:COMPUTERNAME, $env:USERNAME, $Message |
Out-File $script:LogFile -Append
}
if (-not (Test-Path -Path 'C:\temp\vistaupdated.txt')) {
$cmdLine = Join-Path $VistaInsPath $VistaInsEXE
try {
Start-Process -FilePath $cmdLine -ArgumentList $VistaInsParam -Wait
New-Item -Path 'C:\temp' -Type Directory -Force | Out-Null
New-Item -Path 'C:\temp\vistaupdated.txt' -Type File -Value 'Updated' | Out-Null
Write-Log 'Completed successfully.'
} catch {
Write-Log ('Error has occurred: {0}' -f $_.Exception.Message)
}
} else {
Write-Log 'Computer already updated. Aborting.'
}
Arguments could be made for flipping "then" and "else" branch to avoid a negated condition:
if (Test-Path -Path 'C:\temp\vistaupdated.txt') {
Write-Log 'Computer already updated. Aborting.'
} else {
$cmdLine = ...
...
}
or exiting from the script directly if the file exists in order to avoid an else branch entirely:
if (Test-Path -Path 'C:\temp\vistaupdated.txt') {
Write-Log 'Computer already updated. Aborting.'
exit
}
$cmdLine = ...
...
I am running the below script. If the Copy-Item command is successfully completed, it does not show any messages such as how many files are copied. How do I capture this?
Note: I also need to capture the error message which the script is doing correctly.
$LogFile = "P:\users\Logname.log"
$msg = Copy-Item P:\Bkp_20130610\* P:\users -force -recurse -ErrorAction SilentlyContinue
if (-not $?)
{
msg1 = $Error[0].Exception.Message
Write-Host "Encountered error. Error Message is $msg1."
exit
}
$msg > $LogFile
Write-Host "Hello"
You can obtein a list of copied files in this way
$files = copy-item -path $from -destination $to -passthru
pipe it to | ? { -not $_.psiscontainer } if you are copying folder and you don't want them in the count
then use
$files.count
You can use the -Verbose switch with the Copy-Item cmdlet:
$msg=Copy-Item P:\Bkp_20130610\* P:\users -force -recurse -ErrorAction SilentlyContinue -Verbose