$dateTime = Get-Date -Format "yyyyMMdd"
$Logfile = $logfile + $dateTime +".log"
if((Get-ChildItem $Logfile).CreationTime.Date -ne (Get-Date).Date)
{
Write-Host "creating new"
New-Item -Path $Logfile -ItemType File -Force
}
else
{
Write-Host "existing"
}
## This function facilitates in capturing various events into a log file when the script will run
function WriteLog
{
param([string]$Message)
filter timestamp {"$(Get-Date -Format G) $_"}
$Message = $Message | timestamp
Add-content $Logfile -value $Message
}
I am using this small code which will create log file per day if doesn't exist. It append log messages whenever Writelog function is triggered.
Problem facing --> this is working as per expectations for maximum 4 consecutive runs and After this the script is running fine, but not appending any message to the logfile.
If it was me I would define your log path variable once and then use throughout your script. You seem to be trying to access the same name by different methods which will be prone to errors. Your Write-Log method for example should take two parameters: Message and LogFile.
For an easier way to create logs using a Transcript, I have a logs sub folder and I just top and tail my script with the following:
# Start logging
#######################################################################
$log_path = $PSScriptRoot | Join-Path -ChildPath 'Logs'
$script_name = $PSCommandPath -replace '^.*\\([^\\]+)\.ps1$', '$1'
$log_name = '{0}_{1:yyyyMMddhhmmss}.log' -f $script_name, (Get-Date)
$log = $log_path | Join-Path -ChildPath $log_name
Start-Transcript -Path $log -Append -IncludeInvocationHeader
# Do stuff
# Use Write-Host for log comments
# Captures errors without additional code
# Stop logging
#######################################################################
Stop-Transcript
Related
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 wanted to write my own Logging for my scripts.
But there is this smart sentences out there "Do not invent the wheel again"
So I was searching throug the web and found some solutions.
But they seem either to simple (like Write-Host into a csv) or to complicated for me to work with it. (Complex with error levels etc).
All I need for myself and my skill level is something like
Create File --> C:\test\log.txt
Write-Log "Removing file $item was successfull"
New-Item -Path "C:\test\" -Name "ServiceStopped.txt" -ItemType file
Function Write-Log
{
Param ([string]$logstring)
$timestamp = Get-Date -Format "dd-MM-yyyy HH:mm:ss"
$log = "[$timestamp]: $logstring"
$log >> "ServiceStopped.txt"
}
I would like to have an output like
[26.04.2019 12:40]: This has been done
[26.04.2019 12:40]: That has been done
[26.04.2019 12:40]: Working on $a
[26.04.2019 12:40]: Failed while working $b
EDIT:
After the friendly tipps in comments I got myself this
$Logpath = "C:\test\"
$logname = "ServiceStopped.txt"
New-Item -Path $Logpath -Name $logname -ItemType file
Function Write-Log
{
Param ([string]$logstring)
$timestamp = Get-Date -Format "dd-MM-yyyy HH:mm:ss"
$log = "[$timestamp]: $logstring"
Add-Content -Value $log -Path "$Logpath\$logname"
}
Seems to get the output like I wanted it
Looking further to mentiond $Error[0]
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 had made an initial question here Which was answered but as i move along in my task I'm running into another problem.
Summary: I have a log file that's being written to via a serial device. I'm wanting to monitor this log file for particular strings (events) and when they happen i want to write those strings to a separate file.
Executing this one off does what I'm looking for:
$p = #("AC/BATT_PWR","COMM-FAULT")
$fileName = "SRAS_$(Get-Date -format yyyy-MM-dd).log"
$fullPath = "C:\temp\SRAS\$fileName"
Get-Content $fullpath -tail 1 -Wait | Select-String -Pattern $p -SimpleMatch | Out-File -Filepath C:\temp\SRAS\sras_pages.log -Append
The problem is the logfile gets a datestamp, putty saves it as SRAS_yyyy-mm-dd.log. So when the clock passes midnight this will no longer be looking at the correct file.
I found this post on SO which is exactly what I want to do, the OP claims it works for him. I modified it slightly for my purposes but it doesn't write events matching the desired strings to sras_pages.log
This is the 'modified' code:
while($true)
{
$now = Get-Date
$fileName = "SRAS_$(Get-Date -format yyyy-MM-dd).log"
$fullPath = "C:\temp\SRAS\$fileName"
$p = #("AC/BATT_PWR","COMM-FAULT")
Write-Host "[$(Get-Date)] Starting job for file $fullPath"
$latest = Start-Job -Arg $fullPath -ScriptBlock {
param($file)
# wait until the file exists, just in case
while(-not (Test-Path $fullpath)){ sleep -sec 10 }
Get-Content $file -Tail 1 -wait | Select-String -Pattern $p |
foreach { Out-File -Filepath "C:\temp\SRAS\sras_pages.log" -Append }
}
# wait until day changes, or whatever would cause new log file to be created
while($now.Date -eq (Get-Date).Date){ sleep -Sec 10 }
# kill the job and start over
Write-Host "[$(Get-Date)] Stopping job for file $fullPath"
$latest | Stop-Job
}
If I execute just the Get-Content segment of that code it does exactly what I'm looking for. I can't figure out what the issue is.
TIA for advice.
Here is a few suggested changes that should make it work:
$p does not exist within the job, add it as a parameter ($pattern in my example)
You are referring to $fullpath within your job (row 13), it should be $file.
Add parameter -SimpleMatch to select-string to search for literal strings instead of regular expressions. (This is not needed but will come in handy if you change search pattern)
Referring to $pattern instead of $p (see 1)
Skip the foreach on row 16.
Like this:
while($true)
{
$now = Get-Date
$fileName = "SRAS_$(Get-Date -format yyyy-MM-dd).log"
$fullPath = "C:\temp\SRAS\$fileName"
$p = #("AC/BATT_PWR","COMM-FAULT")
Write-Host "[$(Get-Date)] Starting job for file $fullPath"
$latest = Start-Job -Arg $fullPath, $p -ScriptBlock {
param($file,$pattern)
# wait until the file exists, just in case
while(-not (Test-Path $file)){ sleep -sec 10 }
Get-Content $file -Tail 1 -wait | Select-String -Pattern $pattern -SimpleMatch |
Out-File -Filepath "C:\temp\SRAS\sras_pages.log" -Append
}
# wait until day changes, or whatever would cause new log file to be created
while($now.Date -eq (Get-Date).Date){ sleep -Sec 10 }
# kill the job and start over
Write-Host "[$(Get-Date)] Stopping job for file $fullPath"
$latest | Stop-Job
}
I created a PowerShell script to remove all files and folders older than X days. This works perfectly fine and the logging is also ok. Because PowerShell is a bit slow, it can take some time to delete these files and folders when big quantities are to be treated.
My questions: How can I have this script ran on multiple directories ($Target) at the same time?
Ideally, we would like to have this in a scheduled task on Win 2008 R2 server and have an input file (txt, csv) to paste some new target locations in.
Thank you for your help/advise.
The script
#================= VARIABLES ==================================================
$Target = \\share\dir1"
$OlderThanDays = "10"
$Logfile = "$Target\Auto_Clean.log"
#================= BODY =======================================================
# Set start time
$StartTime = (Get-Date).ToShortDateString()+", "+(Get-Date).ToLongTimeString()
Write-Output "`nDeleting folders that are older than $OlderThanDays days:`n" | Tee-Object $LogFile -Append
Get-ChildItem -Directory -Path $Target |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$OlderThanDays) } | ForEach {
$Folder = $_.FullName
Remove-Item $Folder -Recurse -Force -ErrorAction SilentlyContinue
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
# If folder can't be removed
if (Test-Path $Folder)
{ "$Timestamp | FAILLED: $Folder (IN USE)" }
else
{ "$Timestamp | REMOVED: $Folder" }
} | Tee-Object $LogFile -Append # Output folder names to console & logfile at the same time
# Set end time & calculate runtime
$EndTime = (Get-Date).ToShortDateString()+", "+(Get-Date).ToLongTimeString()
$TimeTaken = New-TimeSpan -Start $StartTime -End $EndTime
# Write footer to log
Write-Output ($Footer = #"
Start Time : $StartTime
End Time : $EndTime
Total runtime : $TimeTaken
$("-"*79)
"#)
# Create logfile
Out-File -FilePath $LogFile -Append -InputObject $Footer
# Clean up variables at end of script
$Target=$StartTime=$EndTime=$OlderThanDays = $null
One way to achieve this would be to write an "outer" script that passes the directory-to-be-cleaned, into the "inner" script, as a parameter.
For your "outer" script, have something like this:
$DirectoryList = Get-Content -Path $PSScriptRoot\DirList;
foreach ($Directory in $DirectoryList) {
Start-Process -FilePath powershell.exe -ArgumentList ('"{0}\InnerScript.ps1" -Path "{1}"' -f $PSScriptRoot, $Directory);
}
Note: Using Start-Process kicks off a new process that is, by default, asynchronous. If you use the -Wait parameter, then the process will run synchronously. Since you want things to run more quickly and asynchronously, omitting the -Wait parameter should achieve the desired results.
Invoke-Command
Alternatively, you could use Invoke-Command to kick off a PowerShell script, using the parameters: -File, -ArgumentList, -ThrottleLimit, and -AsJob. The Invoke-Command command relies on PowerShell Remoting, so that must enabled, at least on the local machine.
Add a parameter block to the top of your "inner" script (the one you posted above), like so:
param (
[Parameter(Mandatory = $true)]
[string] $Path
)
That way, your "outer" script can pass in the directory path, using the -Path parameter for the "inner" script.