PowerShell Switch parameter requires input - powershell

I'm a bit in the dark on what would be best practice. I'm creating a function to delete files older than, but I would like to add a switch called -Remote that is optional, but when you choose to use it, the switch requires mandatory information like $Server to have the whole function below executed on the remote server by using the Invoke-Command.
Something like this:
Delete-OldFiles -Target "\\Share\Dir1" -OlderThanDays "10" -LogName "Auto_Clean.log" -Remote "SERVER1"
The script/function
Function Delete-OldFiles
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True,Position=1)]
[ValidateScript({Test-Path $_})]
[String]$Target,
[Parameter(Mandatory=$True,Position=2)]
[Int]$OlderThanDays,
[Parameter(Mandatory=$True,Position=3)]
[String]$LogName
)
if ($PSVersionTable.PSVersion.Major -ge "3") {
# PowerShell 3+ Remove files older than (FASTER)
Get-ChildItem -Path $Target -Exclude $LogName -Recurse -File |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$OlderThanDays) } | ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
# If files can't be removed
if (Test-Path $Item)
{ "$Timestamp | FAILLED: $Item (IN USE)" }
else
{ "$Timestamp | REMOVED: $Item" }
} | Tee-Object $Target\$LogName -Append } # Output file names to console & logfile at the same time
Else {
# PowerShell 2 Remove files older than
Get-ChildItem -Path $Target -Exclude $LogName -Recurse |
Where-Object { !$_.PSIsContainer -and $_.LastWriteTime -lt (Get-Date).AddDays(-$OlderThanDays) } | ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
# If files can't be removed
if (Test-Path $Item)
{
Write-Host "$Timestamp | FAILLED: $Item (IN USE)"
"$Timestamp | FAILLED: $Item (IN USE)"
}
else
{
Write-Host "$Timestamp | REMOVED: $Item"
"$Timestamp | REMOVED: $Item"
}
} | Out-File $Target\$LogName -Append }
}
Delete-OldFiles -Target "\\Share\Dir1" -OlderThanDays "10" -LogName "Auto_Clean.log"
#Delete-OldFiles "E:\Share\Dir1" "5" "Auto_Clean.log"
When I master this I can make the $LogName (logfile) optional to. Thank you for your help. I'm still new to PowerShell and trying to figure this stuff out.

You can use parameters like this
Param (
[switch] $Remote = $false,
[string] $server = $(
if ($Remote)
{
Read-Host -Prompt "Enter remote server:"
}
)
)
In this case, if you call script without -Remote, $server will remain $null.
If you'll call script.ps1 -Remote, it will ask you to enter server name.
If you'll use it like scripts.ps1 -Remote -server "Servername", $server will become Servername.
It can be complicated to wrap function into Invoke-Command based on switch, but you can always use Invoke-Command (it should be as fast as direct command), just use parameters like this
Param (
[switch] $Remote = $false,
[string] $server = $(
if ($Remote)
{
Read-Host -Prompt "Enter remote server:"
}
else
{
"localhost"
}
)
)

Related

Loop in powershell

Hello Guys need some help, tips with script:
$path = ".\" # path do txt
$server = "server" # server.txt
$paczki = ".\paczki\"
$missingi = "$path\$server.txt"
$plik = get-content $missingi
foreach ($j in $plik) {
Write-Output "1"
$wynik = Get-ChildItem "$paczki" | ? {$_.name -match "$j"}
if ($wynik -eq $null) {
# Write-Host $i
}
else {
Write-Output "2"
Write-Host $wynik "znaleziono"
Copy-Item $paczki\$wynik -Destination \\$server\c$\temp\ -force
}
}
#### BAT GENERATOR #####
Write-Output "3"
# & .\bat_generator.ps1
$zapis = "$path\test.bat"
"pushd %~dp0" > $zapis
$nazwa = Get-ChildItem "\\$server\c$\temp\" | select name
foreach ($i in $nazwa) {
$text = $i.name + " /norestart /quiet"
$text >> $zapis
}
"ppd0" >> $zapis # dodaj ppd0
move-item -path .\test.bat -destination \\$server\c$\temp\ -Force # skopiuj .bat na server
At first I create file with name of server, for example server.txt in this server we have list of KBs. Scripts searching in folder paczki that KB exist if yes copying this in server and create .bat
I would like do add automatically searching all .txt files eg server.txt, & server1.txt and use it in loop, I thought about something like that:
$pliki_txt= Get-ChildItem $path -Filter "*.txt" | % {$_.BaseName}
and put it in loop but its not really working, I try to add loop in this place:
for ($i in pliki_txt)
$path = ".\" # path do txt
$server="server" # server.txt
$pliki_txt= Get-ChildItem $path -Filter "*.txt" | % {$_.BaseName}
(....)
What am I doing wrong? Is there any easier way? Script is only working when I put manually set $server like $server="serwer"
You can try this:
$path = ".\"
Get-ChildItem $path -Filter *.txt | %{
$content = Get-content $_.FullName
Foreach($server in $content){
write-host $server
}
}
If I got that right, the issue here is that you're not putting the lines in the right order.
From your original code I would change the following
$path = ".\" # path do txt
$server = "server" # server.txt
$paczki = ".\paczki\"
# $missingi = "$path\$server.txt"
$missingi = Get-ChildItem -Path $path -Filter server*.txt | Select -ExpandProperty Name
foreach ($m in $missingi) {
$plik = get-content $m
( ... )
}
That way you'll check every server*.txt file in that path and process it accordingly.
Or you could even turn it into a parameterized script like this
Param(
[Parameter(Mandatory = $true)]
[String]$path,
[Parameter(Mandatory = $true)]
[String]$pattern,
[Parameter(Mandatory = $true)]
[String]$packzi
)
$missingi = Get-ChildItem -Path $path -Filter *.txt | Select -ExpandProperty | Select-String "$pattern"
foreach ($m in $missingi) {
$plik = get-content $m
foreach ($j in $plik) {
Write-Output "1"
$wynik = Get-ChildItem "$paczki" | ? {$_.name -match "$j"}
if ($wynik -eq $null) {
# Write-Host $i
}
else {
Write-Output "2"
Write-Host $wynik "znaleziono"
Copy-Item $paczki\$wynik -Destination \\$server\c$\temp\ -force
}
}
#### BAT GENERATOR #####
Write-Output "3"
# & .\bat_generator.ps1
$zapis = "$path\test.bat"
"pushd %~dp0" > $zapis
$nazwa = Get-ChildItem "\\$server\c$\temp\" | select name
foreach ($i in $nazwa) {
$text = $i.name + " /norestart /quiet"
$text >> $zapis
}
"ppd0" >> $zapis # dodaj ppd0
move-item -path .\test.bat -destination \\$server\c$\temp\ -Force # skopiuj .bat na server
}
Then you would run it like this:
.\YourScript.ps1 -path ".\" -pattern "server" -packzi ".\packzi\"
That will give you more flexibility if you want to change the source path, the name pattern or the search patch.
I hope this helps.

Files not getting deleted from remote servers

I have written the following script using a post on this forum. The script deletes the files which are older than 15 days:
cls
$servers = Get-Content server.txt
$limit = (Get-Date).AddDays(-15)
$path = "E:\CheckDBOutput"
ForEach ($line in $servers)
{
Invoke-Command -cn $line -ScriptBlock {
Get-ChildItem -Path $path -Recurse -Force | Where-Object {
!$_.PSIsContainer -and $_.CreationTime -lt $limit
} | Remove-Item -Force
}
}
The script is executing fine, but no files are getting deleted.
As already mentioned in the comments, you need to pass the parameters to use inside the scriptblock as arguments to the -ArgumentList parameter:
$RemoveOldFiles = {
param(
[string]$path,
[datetime]$limit
)
Get-ChildItem -Path $path -Recurse -Force | Where-Object {
!$_.PSIsContainer -and $_.CreationTime -lt $limit
} | Remove-Item -Force
}
$servers = Get-Content server.txt
$limit = (Get-Date).AddDays(-15)
$path = "E:\CheckDBOutput"
ForEach ($line in $servers)
{
Invoke-Command -cn $line -ScriptBlock $RemoveOldFiles -ArgumentList $path,$limit
}
Remove-Item does not exist in PowerShell version 1.0 - make sure your destination servers have v2.0 or better installed.

PowerShell Default value not working

I'm trying to feed my function with some variables and when no variable is given it should use a default value of 30 for OlderThanDays. For one reason or another this is not working out as I'd expected.
I fixed my problem in the ForEach loop by using if($_.B -ne $null) {$OlderThanDays=$_.B} else {$OlderThanDays="30"} But I don't think this is best practice. Can anyone tell me why [Int]$OlderThanDays=30 isn't working?
Problem: When adding a line in my csv-file without defining the OlderThanDaysvariable, the default of 30 days is not used and the files are just deleted...
Thank you for your help.
CSV file:
# Correct input formats are:
#
# ServerName, LocalPath, OlderThanDays
# Ex: server, E:\SHARE\Target, 10
# Ex: server, E:\CBR\SHARE\Target
#
# UNC-Path, OlderThanDays
# Ex: \\domain\SHARE\Target, 20
# Ex: \\domain\Target
#
# If no 'OlderThanDays' is provided, a default of 30 days will be used
# Commenting out can be done with '#'
# ______________________________________________________________________
SERVER1, E:\SHARE\Target
\\domain\SHARE\Target2
Full script:
#__________________________________________________________________________________________________________________________________
$ImportFile = "S:\Input\Scheduled Task\Auto_Clean.csv"
$Password = cat "S:\Input\pwd.txt" | ConvertTo-SecureString -Force
$UserName = "domain\me"
#__________________________________________________________________________________________________________________________________
# Scriptblock for running the function in a job
$JobCall = {
# Function that removes files older than x days in all subfolders
Function Delete-OldFiles {
[CmdletBinding(SupportsShouldProcess=$True)]
Param(
[Parameter(Mandatory=$True,Position=1)]
[ValidateScript({Test-Path $_})]
[String]$Target,
[Parameter(Mandatory=$False,Position=2)]
[Int]$OlderThanDays=30,
[Parameter(Mandatory=$False,Position=3)]
[String]$Server,
[switch]$CleanFolders
)
#__________________________________________________________________________________________________________________________________
# Create logfile
$TempDate = (get-date).ToString("dd-MM-yyyy")
$TempFolderPath = $Target -replace '\\','_'
$TempFolderPath = $TempFolderPath -replace ':',''
$TempFolderPath = $TempFolderPath -replace ' ',''
$script:LogFile = "\\DEUSTHEIDIT02\Log\Scheduled Task\Auto_Clean\$Server - $TempFolderPath - $TempDate.log"
#__________________________________________________________________________________________________________________________________
# Check the version of PowerShell
if ($PSVersionTable.PSVersion.Major -ge "3") {
# PowerShell 3+ Remove files older than (FASTER)
Get-ChildItem -Path $Target -Recurse -File |
Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-$OlderThanDays) } |
ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
# Log succes/failure
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
if (Test-Path $Item) {
"$Timestamp | FAILLED: $Server $Item (IN USE)"
}
else {
"$Timestamp | REMOVED: $Server $Item"
}
} | Tee-Object $LogFile -Append -Verbose}
Else {
# PowerShell 2 Remove files older than
Get-ChildItem -Path $Target -Recurse |
Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt (Get-Date).AddDays(-$OlderThanDays) } |
ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
# Log succes/failure
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
if (Test-Path $Item) {
Write-Host "$Timestamp | FAILLED: $Server $Item (IN USE)"
"$Timestamp | FAILLED: $Server $Item (IN USE)"
}
else {
Write-Host "$Timestamp | REMOVED: $Server $Item"
"$Timestamp | REMOVED: $Server $Item"
}
} | Out-File $LogFile -Append }
#__________________________________________________________________________________________________________________________________
# Switch -CleanFolders deletes empty folders older than x days
if ($CleanFolders) {
# Check the version of PowerShell
if ($PSVersionTable.PSVersion.Major -ge "3") {
# PowerShell 3+ Remove empty folders older than (FASTER)
Get-ChildItem -Path $Target -Recurse -Force -Directory -ErrorAction SilentlyContinue |
Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-$OlderThanDays) } |
Where-Object { (Get-ChildItem -Path $_.FullName -Recurse -Force -File) -eq $null } |
ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
# Log succes/failure
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
if (Test-Path $Item) {
"$Timestamp | FAILLED: $Server $Item (IN USE)"
}
else {
"$Timestamp | REMOVED: $Server $Item"
}
} | Tee-Object $LogFile -Append
}
else {
# PowerShell 2 Remove empty folders older than
Get-ChildItem -Path $Target -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { $_.PSIsContainer -and (Get-ChildItem -Path $_.FullName -Recurse -Force | Where-Object { !$_.PSIsContainer }) -eq $null } |
Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-$OlderThanDays) } |
ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
# Log succes/failure
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
if (Test-Path $Item) {
Write-Host "$Timestamp | FAILLED: $Server $Item (IN USE)"
"$Timestamp | FAILLED: $Server $Item (IN USE)"
}
else {
Write-Host "$Timestamp | REMOVED: $Server $Item"
"$Timestamp | REMOVED: $Server $Item"
}
} | Out-File $LogFile -Append
}
}
}
# Lact command of the ScriptBlock: Call the magic to happen
Delete-OldFiles $args[0] $args[1] $args[2] -CleanFolders:$args[3]
}
#__________________________________________________________________________________________________________________________________
# Read input file and ignore all lines starting with #
$File = (Import-Csv -Path $ImportFile -Header "A", "B", "C", "D" | Where { $_.A -NotLike "#*" } )
#__________________________________________________________________________________________________________________________________
# If the UNC Path is provided we will run the script locally else it wil be run on the remote server as a job
Foreach ($_ in $File) {
# Define input format & default values
if ($_.A -like "\\*") {
$Server="UNC"
$Target=$_.A
$OlderThanDays=$_.B
$CleanFolders=$_.C
}
else {
$Server=$_.A
$Target=$_.B
$OlderThanDays=$_.C
$CleanFolders=$_.D
}
# Call the scriptblock with the function to run locally or on the remote server
if ($Server -eq "UNC")
{
Write-Host "UNC Path detected: $Target, $OlderThanDays" -ForegroundColor Yellow
Start-Job -ScriptBlock $JobCall -ArgumentList ($Target, $OlderThanDays, $Server) -Name DelFiles
}
else
{
Write-Host "Local path detected: $Server, $Target, $OlderThanDays" -ForegroundColor Cyan
$Credentials = New-Object System.Management.Automation.PSCredential -ArgumentList $UserName,$Password
Invoke-Command -ScriptBlock $JobCall -ArgumentList ($Target, $OlderThanDays, $Server) -ComputerName "$Server.domain" -Authentication Credssp -Credential $Credentials -AsJob -JobName DelFiles
# Delete empty folders: Invoke-Command -ScriptBlock $JobCall -ArgumentList ($Target, $OlderThanDays, $Server, $true) -ComputerName "$Server.domain" -Authentication Credssp -Credential $Credentials -AsJob -JobName DelFiles
}
}
The parameter can't be both Mandatory and have default value. Former is checked first, and if it's set to $true, than default value is simply ignored. If you want to make sure that users can't specify empty value, just use [ValidateNotNullorEmpty()] validation, and make parameter optional.
So I had a similar problem. After months keeping a module with personal functions for day-to-day activities making extensive use of default values and, in many cases, mandatory parameters with initialized default values, suddenly my functions stopped working.
After writing a new one, calling it with explicit parameters would result in the parameters values being empty in the function execution context.
After restarting powershell console for some times I decided to reboot the machine, and everything went back to normal. Go figure.

PowerShell passing parameter with Invoke-Command

I can't seem to find the correct syntax to pass 2 variables from the CALL-script to the execution script in order to have it executed on the remote server. I tried single quotes, double quotes, brackets, .. nothing I can fiind passes the $Target and $OlderThanDays parameters to the script.
Thank you for your help.
The CALL-Script:
#================= VARIABLES ==================================================
$ScriptDir = "\\Server\Scripts"
#================= BODY =======================================================
# Invoke-Command -ComputerName SERVER1 -FilePath $ScriptDir\"Auto_Clean.ps1"
Invoke-Command -FilePath .\Test.ps1 -ComputerName SERVER01 -ArgumentList {-Target ´E:\Share\Dir1\Dir2´,-OlderThanDays ´10´}
The execution Script:
#================= PARAMETERS =================================================
Param(
[Parameter(Mandatory=$True,Position=1)]
[string]$Target,
[Parameter(Mandatory=$True,Position=2)]
[string]$OlderThanDays
)
#================= BODY =======================================================
# Set start time & logname
$StartTime = (Get-Date).ToShortDateString()+", "+(Get-Date).ToLongTimeString()
$LogName = "Auto_Clean.log"
# Format header for log
$TimeStamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()+" |"
$Header = "`n$TimeStamp Deleting files and folders that are older than $OlderThanDays days:`n"
Write-Output "$Header" # to console
Out-File $Target\$LogName -inputobject $Header -Append # to log
# PS 2.0 Workaround (`tee-object -append`) // PS 4.0: `Write-Output "`nDeleting folders that are older than $OlderThanDays days:`n" | Tee-Object $LogFile -Append`
# Remove files older than
Get-ChildItem -Path $Target -Exclude $LogName -Recurse |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$OlderThanDays) } | ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
# If folder can't be removed
if (Test-Path $Item)
{ "$Timestamp | FAILLED: $Item (IN USE)" }
else
{ "$Timestamp | REMOVED: $Item" }
} | Out-File $Target\$LogName -Append
# PS 4.0: ´| Tee-Object $Target\$LogName -Append` # Output folder names to console & logfile at the same time
# Remove empty folders
while (Get-ChildItem $Target -recurse | where {!#(Get-ChildItem -force $_.FullName)} | Test-Path) {
Get-ChildItem $Target -recurse | where {!#(Get-ChildItem -force $_.FullName)} | Remove-Item
}
# Format footer
$EndTime = (Get-Date).ToShortDateString()+", "+(Get-Date).ToLongTimeString()
$TimeTaken = New-TimeSpan -Start $StartTime -End $EndTime
Write-Output ($Footer = #"
Start Time : $StartTime
End Time : $EndTime
Total Runtime : $TimeTaken
$("-"*79)
"#)
# Write footer to log
Out-File -FilePath $Target\$LogName -Append -InputObject $Footer
# Clean up variables
$Target=$StartTime=$EndTime=$OlderThanDays = $null
The execution script:
you have got to use " or ' but not ´ :
-argumentlist #('E:\Share\Dir1\Dir2',10)

Create Log File in Powershell

I have the below code and currently it loads all the information on screen. I want it to log to a log file on D:\Apps\Logs.
The log file needs to have the name of the computer it is loading against - so COMPUTERNAME.log
Any idea how I can do this?
Thanks
$computer = gc env:computername
$onetcp = ((get-childitem c:\windows\system32\drivers\tcpip.sys).Versioninfo.ProductMajorPart).tostring() $twotcp = ((get-childitem c:\windows\system32\drivers\tcpip.sys).Versioninfo.ProductMinorPart).tostring() $threetcp = ((get-childitem c:\windows\system32\drivers\tcpip.sys).Versioninfo.ProductBuildPart).tostring() $fourtcp = ((get-childitem c:\windows\system32\drivers\tcpip.sys).Versioninfo.ProductPrivatePart).tostring()
$onedfsr = ((get-childitem c:\windows\system32\dfsrs.exe).Versioninfo.ProductMajorPart).tostring() $twodfsr = ((get-childitem c:\windows\system32\dfsrs.exe).Versioninfo.ProductMinorPart).tostring() $threedfsr = ((get-childitem c:\windows\system32\dfsrs.exe).Versioninfo.ProductBuildPart).tostring() $fourdfsr = ((get-childitem c:\windows\system32\dfsrs.exe).Versioninfo.ProductPrivatePart).tostring()
write-host TCPIP.sys Version on $computer is: "$onetcp.$twotcp.$threetcp.$fourtcp" Write-Host write-host DFSRS.exe Version on $computer is: "$onedfsr.$twodfsr.$threedfsr.$fourdfsr"
Write-Host
If (get-wmiobject win32_share | where-object {$_.Name -eq "REMINST"}) { Write-Host "The REMINST share exists on $computer" } Else { Write-Host "The REMINST share DOES NOT exist on $computer - Please create as per standards" } Write-Host
$hotfix1 = Get-HotFix -Id KB2450944 -ErrorAction SilentlyContinue $hotfix2 = Get-HotFix -Id KB2582284 -ErrorAction SilentlyContinue $hotfix3 = Get-HotFix -Id KB979808 -ErrorAction SilentlyContinue
If ($hotfix1) { Write-Host "Hotfix KB2450944 is installed"
-BackgroundColor Green -ForegroundColor Black } else { Write-Host "Hotfix KB2450944 is NOT installed - Please ensure you install this hotfix" -ForegroundColor "red" }
If ($hotfix2) { Write-Host "Hotfix KB2582284 is installed"
-BackgroundColor Green -ForegroundColor Black } else { Write-Host "Hotfix KB2582284 is NOT installed - Please ensure you install this hotfix" -ForegroundColor "red" }
If ($hotfix3) { Write-Host "Hotfix KB979808 is installed"
-BackgroundColor Green -ForegroundColor Black } else { Write-Host "Hotfix KB979808 is NOT installed - Please ensure you install this hotfix" -ForegroundColor "red" }
Put this at the top of your file:
$Logfile = "D:\Apps\Logs\$(gc env:computername).log"
Function LogWrite
{
Param ([string]$logstring)
Add-content $Logfile -value $logstring
}
Then replace your Write-host calls with LogWrite.
A function that takes these principles a little further.
Add's timestamps - can't have a log without timestamps.
Add's a level (uses INFO by default) meaning you can highlight big issues.
Allows for optional console output. If you don't set a log destination, it simply pumps it out.
Function Write-Log {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$False)]
[ValidateSet("INFO","WARN","ERROR","FATAL","DEBUG")]
[String]
$Level = "INFO",
[Parameter(Mandatory=$True)]
[string]
$Message,
[Parameter(Mandatory=$False)]
[string]
$logfile
)
$Stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss")
$Line = "$Stamp $Level $Message"
If($logfile) {
Add-Content $logfile -Value $Line
}
Else {
Write-Output $Line
}
}
I believe this is the simplest way of putting all what it is on the screen into a file. It is a native PS CmdLet so you don't have to change or install anything in your script
Start-Transcript -Path Computer.log
Write-Host "everything will end up in Computer.log"
Stop-Transcript
You can also add -Append to append instead the content [Thanks #scipilot for the tip!]
function WriteLog
{
Param ([string]$LogString)
$LogFile = "C:\$(gc env:computername).log"
$DateTime = "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)
$LogMessage = "$Datetime $LogString"
Add-content $LogFile -value $LogMessage
}
WriteLog "This is my log message"
Using this Log-Entry framework:
Script:
Function Main {
Log -File "D:\Apps\Logs\$Env:computername.log"
$tcp = (get-childitem c:\windows\system32\drivers\tcpip.sys).Versioninfo.ProductVersionRaw
$dfs = (get-childitem C:\Windows\Microsoft.NET\Framework\v2.0.50727\dfsvc.exe).Versioninfo.ProductVersionRaw
Log "TCPIP.sys Version on $computer is:" $tcp
Log "DFSVC.exe Version on $computer is:" $dfs
If (get-wmiobject win32_share | where-object {$_.Name -eq "REMINST"}) {Log "The REMINST share exists on $computer"}
Else {Log "The REMINST share DOES NOT exist on $computer - Please create as per standards"}
"KB2450944", "KB3150513", "KB3176935" | ForEach {
$hotfix = Get-HotFix -Id $_ -ErrorAction SilentlyContinue
If ($hotfix) {Log -Color Green Hotfix $_ is installed}
Else {Log -Color Red Hotfix $_ " is NOT installed - Please ensure you install this hotfix"}
}
}
Screen output:
Log File (at D:\Apps\Logs\<computername>.log):
2017-05-31 Write-Log (version: 01.00.02, PowerShell version: 5.1.14393.1198)
19:19:29.00 C:\Users\User\PowerShell\Write-Log\Check.ps1
19:19:29.47 TCPIP.sys Version on is: {Major: 10, Minor: 0, Build: 14393, Revision: 1066, MajorRevision: 0, MinorRevision: 1066}
19:19:29.50 DFSVC.exe Version on is: {Major: 2, Minor: 0, Build: 50727, Revision: 8745, MajorRevision: 0, MinorRevision: 8745}
19:19:29.60 The REMINST share DOES NOT exist on - Please create as per standards
Error at 25,13: Cannot find the requested hotfix on the 'localhost' computer. Verify the input and run the command again.
19:19:33.41 Hotfix KB2450944 is NOT installed - Please ensure you install this hotfix
19:19:37.03 Hotfix KB3150513 is installed
19:19:40.77 Hotfix KB3176935 is installed
19:19:40.77 End
Gist with log rotation: https://gist.github.com/barsv/85c93b599a763206f47aec150fb41ca0
Usage:
. .\logger.ps1
Write-Log "debug message"
Write-Log "info message" "INFO"
You might just want to use the new TUN.Logging PowerShell module, this can also send a log mail. Just use the Start-Log and/or Start-MailLog cmdlets to start logging and then just use Write-HostLog, Write-WarningLog, Write-VerboseLog, Write-ErrorLog etc. to write to console and log file/mail. Then call Send-Log and/or Stop-Log at the end and voila, you got your logging.
Just install it from the PowerShell Gallery via
Install-Module -Name TUN.Logging
Or just follow the link: https://www.powershellgallery.com/packages/TUN.Logging
Documentation of the module can be found here: https://github.com/echalone/TUN/blob/master/PowerShell/Modules/TUN.Logging/TUN.Logging.md
I've been playing with this code for a while now and I have something that works well for me. Log files are numbered with leading '0' but retain their file extension. And I know everyone likes to make functions for everything but I started to remove functions that performed 1 simple task. Why use many word when few do trick? Will likely remove other functions and perhaps create functions out of other blocks. I keep the logger script in a central share and make a local copy if it has changed, or load it from the central location if needed.
First I import the logger:
#Change directory to the script root
cd $PSScriptRoot
#Make a local copy if changed then Import logger
if(test-path "D:\Scripts\logger.ps1"){
if (Test-Path "\\<server>\share\DCS\Scripts\logger.ps1") {
if((Get-FileHash "\\<server>\share\DCS\Scripts\logger.ps1").Hash -ne (Get-FileHash "D:\Scripts\logger.ps1").Hash){
rename-Item -path "..\logger.ps1" -newname "logger$(Get-Date -format 'yyyyMMdd-HH.mm.ss').ps1" -force
Copy-Item "\\<server>\share\DCS\Scripts\logger.ps1" -destination "..\" -Force
}
}
}else{
Copy-Item "\\<server>\share\DCS\Scripts\logger.ps1" -destination "..\" -Force
}
. "..\logger.ps1"
Define the log file:
$logfile = (get-location).path + "\Log\" + $QProfile.replace(" ","_") + "-$metricEnv-$ScriptName.log"
What I log depends on debug levels that I created:
if ($Debug -ge 1){
$message = "<$pid>Debug:$Debug`-Adding tag `"MetricClass:temp`" to $host_name`:$metric_name"
Write-Log $message $logfile "DEBUG"
}
I would probably consider myself a bit of a "hack" when it comes to coding so this might not be the prettiest but here is my version of logger.ps1:
# all logging settins are here on top
param(
[Parameter(Mandatory=$false)]
[string]$logFile = "$(gc env:computername).log",
[Parameter(Mandatory=$false)]
[string]$logLevel = "DEBUG", # ("DEBUG","INFO","WARN","ERROR","FATAL")
[Parameter(Mandatory=$false)]
[int64]$logSize = 10mb,
[Parameter(Mandatory=$false)]
[int]$logCount = 25
)
# end of settings
function Write-Log-Line ($line, $logFile) {
$logFile | %{
If (Test-Path -Path $_) { Get-Item $_ }
Else { New-Item -Path $_ -Force }
} | Add-Content -Value $Line -erroraction SilentlyCOntinue
}
function Roll-logFile
{
#function checks to see if file in question is larger than the paramater specified if it is it will roll a log and delete the oldes log if there are more than x logs.
param(
[string]$fileName = (Get-Date).toString("yyyy/MM/dd HH:mm:ss")+".log",
[int64]$maxSize = $logSize,
[int]$maxCount = $logCount
)
$logRollStatus = $true
if(test-path $filename) {
$file = Get-ChildItem $filename
# Start the log-roll if the file is big enough
#Write-Log-Line "$Stamp INFO Log file size is $($file.length), max size $maxSize" $logFile
#Write-Host "$Stamp INFO Log file size is $('{0:N0}' -f $file.length), max size $('{0:N0}' -f $maxSize)"
if($file.length -ge $maxSize) {
Write-Log-Line "$Stamp INFO Log file size $('{0:N0}' -f $file.length) is larger than max size $('{0:N0}' -f $maxSize). Rolling log file!" $logFile
#Write-Host "$Stamp INFO Log file size $('{0:N0}' -f $file.length) is larger than max size $('{0:N0}' -f $maxSize). Rolling log file!"
$fileDir = $file.Directory
$fbase = $file.BaseName
$fext = $file.Extension
$fn = $file.name #this gets the name of the file we started with
function refresh-log-files {
Get-ChildItem $filedir | ?{ $_.Extension -match "$fext" -and $_.name -like "$fbase*"} | Sort-Object lastwritetime
}
function fileByIndex($index) {
$fileByIndex = $files | ?{($_.Name).split("-")[-1].trim("$fext") -eq $($index | % tostring 00)}
#Write-Log-Line "LOGGER: fileByIndex = $fileByIndex" $logFile
$fileByIndex
}
function getNumberOfFile($theFile) {
$NumberOfFile = $theFile.Name.split("-")[-1].trim("$fext")
if ($NumberOfFile -match '[a-z]'){
$NumberOfFile = "01"
}
#Write-Log-Line "LOGGER: GetNumberOfFile = $NumberOfFile" $logFile
$NumberOfFile
}
refresh-log-files | %{
[int32]$num = getNumberOfFile $_
Write-Log-Line "LOGGER: checking log file number $num" $logFile
if ([int32]$($num | % tostring 00) -ge $maxCount) {
write-host "Deleting files above log max count $maxCount : $_"
Write-Log-Line "LOGGER: Deleting files above log max count $maxCount : $_" $logFile
Remove-Item $_.fullName
}
}
$files = #(refresh-log-files)
# Now there should be at most $maxCount files, and the highest number is one less than count, unless there are badly named files, eg non-numbers
for ($i = $files.count; $i -gt 0; $i--) {
$newfilename = "$fbase-$($i | % tostring 00)$fext"
#$newfilename = getFileNameByNumber ($i | % tostring 00)
if($i -gt 1) {
$fileToMove = fileByIndex($i-1)
} else {
$fileToMove = $file
}
if (Test-Path $fileToMove.PSPath) { # If there are holes in sequence, file by index might not exist. The 'hole' will shift to next number, as files below hole are moved to fill it
write-host "moving '$fileToMove' => '$newfilename'"
#Write-Log-Line "LOGGER: moving $fileToMove => $newfilename" $logFile
# $fileToMove is a System.IO.FileInfo, but $newfilename is a string. Move-Item takes a string, so we need full path
Move-Item ($fileToMove.FullName) -Destination $fileDir\$newfilename -Force
}
}
} else {
$logRollStatus = $false
}
} else {
$logrollStatus = $false
}
$LogRollStatus
}
Function Write-Log {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[string]
$Message,
[Parameter(Mandatory=$False)]
[String]
$logFile = "log-$(gc env:computername).log",
[Parameter(Mandatory=$False)]
[String]
$Level = "INFO"
)
#Write-Host $logFile
$levels = ("DEBUG","INFO","WARN","ERROR","FATAL")
$logLevelPos = [array]::IndexOf($levels, $logLevel)
$levelPos = [array]::IndexOf($levels, $Level)
$Stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss:fff")
# First roll the log if needed to null to avoid output
$Null = #(
Roll-logFile -fileName $logFile -filesize $logSize -logcount $logCount
)
if ($logLevelPos -lt 0){
Write-Log-Line "$Stamp ERROR Wrong logLevel configuration [$logLevel]" $logFile
}
if ($levelPos -lt 0){
Write-Log-Line "$Stamp ERROR Wrong log level parameter [$Level]" $logFile
}
# if level parameter is wrong or configuration is wrong I still want to see the
# message in log
if ($levelPos -lt $logLevelPos -and $levelPos -ge 0 -and $logLevelPos -ge 0){
return
}
$Line = "$Stamp $Level $Message"
Write-Log-Line $Line $logFile
}