Renaming Variable Output in PowerShell - powershell

I'm trying to write a script to monitor URLs. I've managed to get the information I want, and my script looks like this
$logfile = "C:\LogFileTest.log"
Function WriteHeader-Websites {
[cmdletbinding()]
param (
[string]$URL
)
if ($logfile -eq "nologfile"){
write-host
write-host "$URL"
write-host
}
else {
add-content -path $logfile -value ""
add-content -path $logfile -value "$URL"
add-content -path $logfile -value ""
}
}
Function Test-Websites {
$URLs = Get-Content -Path C:\WebsiteChecks.txt
foreach ($URL in $URLs) {
$ArrayLine =$Line.split(",")
$
$Line.name =
try {
$request = [System.Net.WebRequest]::Create($URL)
$response = $request.GetResponse()
WriteHeader-Websites "$URL is available!"
} catch {
WriteHeader-Websites ("$URL failed! The error message was '{0}'." -f $_)
} finally {
if ($response) {
$response.Close()
Remove-Variable response
}
}
}
}
Test-Websites
My text file looks like this:
http://www.google.com
http://www.bing.com
http://www.bbc.co.uk
And the output from the script looks like this:
http://www.google.com is available!
I'd like to be able to add a short name for the websites in the text file, so it uses that name instead.
An example of the text file would be:
http://www.google.com,Google Website
http://www.bing.com,Bing Website
and I'd like the return to look like:
Google Website is available!
Bing Website is available!
or
Google Website failed! Error message is...
But I have no idea how to do that, or what I'd even Google for to find it out. Any suggestions?
Thanks

The easiest way to do this would be to create a csv with headers, then you can refer to the columns using their headers.
url,description
http://www.google.com,Google Website
http://www.bing.com,Bing Website
Then use Import-Csv to import the file, so you could do something like this:
Import-Csv -Path C:\WebsiteChecks.txt | % {
$_.description
$_.url
}
Update
Here's your code rewritten using Import-Csv notice I put description in it's own variable as $_ would be updated by the exception in your catch statement.
$logfile = "C:\LogFileTest.log"
Function WriteHeader-Websites {
[cmdletbinding()]
param (
[string]$URL
)
if ($logfile -eq "nologfile"){
write-host
write-host "$URL"
write-host
}
else {
add-content -path $logfile -value ""
add-content -path $logfile -value "$URL"
add-content -path $logfile -value ""
}
}
Function Test-Websites {
Import-Csv -Path C:\WebsiteChecks.txt | % {
$url = $_.url
$description = $_.description
try {
$request = [System.Net.WebRequest]::Create($url)
$response = $request.GetResponse()
WriteHeader-Websites "$description is available!"
} catch {
WriteHeader-Websites ("$description failed! The error message was '{0}'." -f $_)
} finally {
if ($response) {
$response.Close()
Remove-Variable response
}
}
}
}
Test-Websites

Related

Is there a way to include filepaths with diacritics in a robocopy script?

I have a script that extracts metadata from each file in a directory. When the filepath is free of diacritics, the script produces a csv file that looks like this:
When the filepath includes a diacritic (ie. "TéstMé.txt"), the csv file has blanks in the filehash field:
My question is: how do I get this script to work regardless of diacritics in the filepath?
I have determined that the problem is not with the Get-FileHash part of the script (When I run the single line Get-FileHash "C:\Temp\New\TéstMé.txt" a hash is produced.)
I have also determined that replacing FileHash = Get-FileHash -Path with FileHash = Get-FileHash -LiteralPath is not a solution, as it also produces a blank.
I tried to change the regex in the line ($_.Trim() -match "^(?<Children>\d+)\s+(?<FullName>.*)") { in case it was blocking diacritics, but any change would bring up WARNING: parsing [unique parsing error here].
I also tried to change ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True from $true to $false (in case the pipeline was changing the filepath value) but that had no effect.
I thought maybe Robocopy (which is used in the script) was incapable of handling files with diacritics, but Robocopy C:\Temp\New C:\Temp\star moves the files fine.
I do have a regex for identifying illegal characters (obtained from here) but I don't know how to incorporate it into the script.
FYI: I cannot change the actual file names. Would love to do a find-and-replace for any letter with a diacritic, but this option isn't open to me.
Function Get-FolderItem {
[cmdletbinding(DefaultParameterSetName='Filter')]
Param (
[parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[Alias('FullName')]
[string[]]$Path = $PWD,
[parameter(ParameterSetName='Filter')]
[string[]]$Filter = '*.*',
[parameter(ParameterSetName='Exclude')]
[string[]]$ExcludeFile,
[parameter()]
[int]$MaxAge,
[parameter()]
[int]$MinAge
)
Begin {
$params = New-Object System.Collections.Arraylist
$params.AddRange(#("/L","/E","/NJH","/BYTES","/FP","/NC","/XJ","/R:0","/W:0","T:W"))
If ($PSBoundParameters['MaxAge']) {
$params.Add("/MaxAge:$MaxAge") | Out-Null
}
If ($PSBoundParameters['MinAge']) {
$params.Add("/MinAge:$MinAge") | Out-Null
}
}
Process {
ForEach ($item in $Path) {
Try {
$item = (Resolve-Path -LiteralPath $item -ErrorAction Stop).ProviderPath
If (-Not (Test-Path -LiteralPath $item -Type Container -ErrorAction Stop)) {
Write-Warning ("{0} is not a directory and will be skipped" -f $item)
Return
}
If ($PSBoundParameters['ExcludeFile']) {
$Script = "robocopy `"$item`" NULL $Filter $params /XF $($ExcludeFile -join ',')"
} Else {
$Script = "robocopy `"$item`" NULL $Filter $params"
}
Write-Verbose ("Scanning {0}" -f $item)
Invoke-Expression $Script | ForEach {
Try {
If ($_.Trim() -match "^(?<Children>\d+)\s(?<FullName>.*)") {
$object = New-Object PSObject -Property #{
FullName = $matches.FullName
Extension = $matches.fullname -replace '.*\.(.*)','$1'
FullPathLength = [int] $matches.FullName.Length
FileHash = Get-FileHash -LiteralPath "\\?\$($matches.FullName)" |Select -Expand Hash
Created = ([System.IO.FileInfo] $matches.FullName).creationtime
LastWriteTime = ([System.IO.FileInfo] $matches.FullName).LastWriteTime
}
$object.pstypenames.insert(0,'System.IO.RobocopyDirectoryInfo')
Write-Output $object
} Else {
Write-Verbose ("Not matched: {0}" -f $_)
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
}
}
Get-FolderItem "C:\Temp\New" | Export-Csv -Path C:\Temp\testesting.csv
Here is a solution, I output the RoboCopy output to an unicode log using /UNILOG:c:\temp\test.txt params and then use the same code
Function Get-FolderItem {
[cmdletbinding(DefaultParameterSetName='Filter')]
Param (
[parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[Alias('FullName')]
[string[]]$Path = $PWD,
[parameter(ParameterSetName='Filter')]
[string[]]$Filter = '*.*',
[parameter(ParameterSetName='Exclude')]
[string[]]$ExcludeFile,
[parameter()]
[int]$MaxAge,
[parameter()]
[int]$MinAge
)
Begin {
$params = New-Object System.Collections.Arraylist
$params.AddRange(#("/L","/E","/NJH","/BYTES","/FP","/NC","/XJ","/R:0","/W:0","T:W","/UNILOG:c:\temp\test.txt"))
If ($PSBoundParameters['MaxAge']) {
$params.Add("/MaxAge:$MaxAge") | Out-Null
}
If ($PSBoundParameters['MinAge']) {
$params.Add("/MinAge:$MinAge") | Out-Null
}
}
Process {
ForEach ($item in $Path) {
Try {
$item = (Resolve-Path -LiteralPath $item -ErrorAction Stop).ProviderPath
If (-Not (Test-Path -LiteralPath $item -Type Container -ErrorAction Stop)) {
Write-Warning ("{0} is not a directory and will be skipped" -f $item)
Return
}
If ($PSBoundParameters['ExcludeFile']) {
$Script = "robocopy `"$item`" NULL $Filter $params /XF $($ExcludeFile -join ',')"
} Else {
$Script = "robocopy `"$item`" NULL $Filter $params"
}
Write-Verbose ("Scanning {0}" -f $item)
Invoke-Expression $Script | Out-Null
get-content "c:\temp\test.txt" | ForEach {
Try {
If ($_.Trim() -match "^(?<Children>\d+)\s(?<FullName>.*)") {
$object = New-Object PSObject -Property #{
FullName = $matches.FullName
Extension = $matches.fullname -replace '.*\.(.*)','$1'
FullPathLength = [int] $matches.FullName.Length
FileHash = Get-FileHash -LiteralPath "\\?\$($matches.FullName)" |Select -Expand Hash
Created = ([System.IO.FileInfo] $matches.FullName).creationtime
LastWriteTime = ([System.IO.FileInfo] $matches.FullName).LastWriteTime
}
$object.pstypenames.insert(0,'System.IO.RobocopyDirectoryInfo')
Write-Output $object
} Else {
Write-Verbose ("Not matched: {0}" -f $_)
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
}
}
$a = Get-FolderItem "C:\Temp\New" | Export-Csv -Path C:\Temp\testtete.csv -Encoding Unicode

How to make Powershell Extract Metadata script run faster?

I have a script that I use to extract metadata from files in a network directory. It originates here, and I modified it in order to obtain additional metadata (size of file, filehash, date created,and lastwritetime), but these additions appear to slow down the script to the point that it takes weeks to complete when the number of files is more than 10000.
To illustrate the impact of the script additions on the speed, I ran the script on a folder containing five documents:
original script (no get-item or get-file hash lines): 2.9794699 seconds
with 'get-item' lines (size, filehash, created, lastwritetime): 7.6295035 seconds
with 'get-filehash' line : 6.9363834 seconds
with 'get-item' lines and 'get-filehash' lines: 12.4516334 seconds
I tried putting all the get-item lines together in a for-loop thinking that it would be faster to retrieve the file once from the network, then extract the metadata. While this modified script runs at a much faster 8.6488492 seconds, the metadata fields are not included in the output.
Here's the original script:
#Works on Powershell version 5.1
#The filepath of the folder being printed and the filepath where the output file will be placed need to be specified in the last line of script.
Function Get-FolderItem {
[cmdletbinding(DefaultParameterSetName='Filter')]
Param (
[parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[Alias('FullName')]
[string[]]$Path = $PWD,
[parameter(ParameterSetName='Filter')]
[string[]]$Filter = '*.*',
[parameter(ParameterSetName='Exclude')]
[string[]]$ExcludeFile,
[parameter()]
[int]$MaxAge,
[parameter()]
[int]$MinAge
)
Begin {
$params = New-Object System.Collections.Arraylist
$params.AddRange(#("/L","/E","/NJH","/NDL","/BYTES","/FP","/NC","/XJ","/R:0","/W:0","T:W"))
If ($PSBoundParameters['MaxAge']) {
$params.Add("/MaxAge:$MaxAge") | Out-Null
}
If ($PSBoundParameters['MinAge']) {
$params.Add("/MinAge:$MinAge") | Out-Null
}
}
Process {
ForEach ($item in $Path) {
Try {
$item = (Resolve-Path -LiteralPath $item -ErrorAction Stop).ProviderPath
If (-Not (Test-Path -LiteralPath $item -Type Container -ErrorAction Stop)) {
Write-Warning ("{0} is not a directory and will be skipped" -f $item)
Return
}
If ($PSBoundParameters['ExcludeFile']) {
$Script = "robocopy `"$item`" NULL $Filter $params /XF $($ExcludeFile -join ',')"
} Else {
$Script = "robocopy `"$item`" NULL $Filter $params"
}
Write-Verbose ("Scanning {0}" -f $item)
Invoke-Expression $Script | ForEach {
Try {
If ($_.Trim() -match "^(?<Children>\d+)\s+(?<FullName>.*)") {
$object = New-Object PSObject -Property #{
FullName = $matches.FullName
Extension = $matches.fullname -replace '.*\.(.*)','$1'
FullPathLength = [int] $matches.FullName.Length
Stuff = foreach {$matches in $match}Length = (Get-Item $matches.FullName).length
FileHash = Get-FileHash -Path "\\?\$($matches.FullName)" |Select -Expand Hash
Created = (Get-Item $matches.FullName).creationtime
LastWriteTime = (Get-Item $matches.FullName).LastWriteTime
Owner = (Get-ACL $matches.Fullname).Owner
}
$object.pstypenames.insert(0,'System.IO.RobocopyDirectoryInfo')
Write-Output $object
} Else {
Write-Verbose ("Not matched: {0}" -f $_)
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
}
}
Get-FolderItem "O:\directory\to\files" | Export-Csv -Path C:\output.csv
Does anyone know how to make the script run faster?
Rather than (Get-Item $matches.FullName).length you could do ([System.IO.FileInfo]$Matches.FullName).length. I see much better performance from that (Get-Item taking about 3.5x longer). Same for LastWriteTime.

Calculate the hash of a file longer than 256 characters?

I am using Boe Prox's script to print a list of all the files and folders in a directory. I need Prox's script (as opposed to other windows print directory commands) because it uses robocopy to print filepaths longer than 260 characters.
My problem is, I also want the filehash to be printed alongside the file name. I have modified the script to obtain hashes (please see below) and it generally works except when the filepath is longer than 260 characters. Long filepaths get a blank in the hash column of the final output.
Research I have done:
According to this stackoverflow question, Robocopy has several switches that can be modified. I have scoured Boe's blog, as well as the list of robocopy commands, but there is nothing about a filehash switch.
Attempts to fix the problem:
I have also tried to modify the syntax of the filehash to make it more in line with the rest of the script ie. Hash = $matches.Hash
(this returns all blanks in place of the filehashs)
I tried taking off the part of the regex that seems to specify an item rather than the content of the item ie:If ($_.Trim() -match "^(?<Children>\d+)\s+") {
(this leads to the error code WARNING: Cannot bind argument to parameter 'Path' because it is null.)
I'm pretty hopeful that this can happen though: comments in Boe's original script includes the line: "Version 1.1 -Added ability to calculate file hashes"
Here's is my (partially working script):
Function Get-FolderItem {
[cmdletbinding(DefaultParameterSetName='Filter')]
Param (
[parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[Alias('FullName')]
[string[]]$Path = $PWD,
[parameter(ParameterSetName='Filter')]
[string[]]$Filter = '*.*',
[parameter(ParameterSetName='Exclude')]
[string[]]$ExcludeFile,
[parameter()]
[int]$MaxAge,
[parameter()]
[int]$MinAge
)
Begin {
$params = New-Object System.Collections.Arraylist
$params.AddRange(#("/L","/E","/NJH","/BYTES","/FP","/NC","/XJ","/R:0","/W:0","T:W"))
If ($PSBoundParameters['MaxAge']) {
$params.Add("/MaxAge:$MaxAge") | Out-Null
}
If ($PSBoundParameters['MinAge']) {
$params.Add("/MinAge:$MinAge") | Out-Null
}
}
Process {
ForEach ($item in $Path) {
Try {
$item = (Resolve-Path -LiteralPath $item -ErrorAction Stop).ProviderPath
If (-Not (Test-Path -LiteralPath $item -Type Container -ErrorAction Stop)) {
Write-Warning ("{0} is not a directory and will be skipped" -f $item)
Return
}
If ($PSBoundParameters['ExcludeFile']) {
$Script = "robocopy `"$item`" NULL $Filter $params /XF $($ExcludeFile -join ',')"
} Else {
$Script = "robocopy `"$item`" NULL $Filter $params"
}
Write-Verbose ("Scanning {0}" -f $item)
Invoke-Expression $Script | ForEach {
Try {
If ($_.Trim() -match "^(?<Children>\d+)\s+(?<FullName>.*)") {
$object = New-Object PSObject -Property #{
FullName = $matches.FullName
Extension = $matches.fullname -replace '.*\.(.*)','$1'
FullPathLength = [int] $matches.FullName.Length
Length = [int64]$matches.Size
FileHash = (Get-FileHash -Path $matches.FullName).Hash
Created = (Get-Item $matches.FullName).creationtime
LastWriteTime = (Get-Item $matches.FullName).LastWriteTime
}
$object.pstypenames.insert(0,'System.IO.RobocopyDirectoryInfo')
Write-Output $object
} Else {
Write-Verbose ("Not matched: {0}" -f $_)
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
}
}
Get-FolderItem "C:\TestingFileFolders"
In Windows PowerShell, you can prepend \\?\ to the full path and read the file:
Get-FileHash -Path "\\?\$($matches.FullName)"

What is the easiest way to create log file in powershell?

I have a code which modify user attribute in active directory.
How can I modify the code, to see in a log file what changed? I would like to see too if there was an error or not. If was an error what was that.
The code:
$csvdata = Import-Csv $csv -Delimiter $delimiter -Encoding "UTF8"
ForEach-Object -InputObject $csvdata {
$params = #{Identity = $_.ObjectGUID}
$sn = $_.Surname.Trim()
$gn = $_.GivenName.Trim()
$Manager = $_.Manager
if (-not [string]::IsNullOrWhiteSpace($_.Surname)) {
$params.Surname = $_.Surname
}
if (-not [string]::IsNullOrWhiteSpace($_.Givenname)) {
$params.Givenname = $_.Givenname
}
Set-ADUser #params
}
You can use transcript commands.
Start-Transcript
& ".\Script.ps1"
Stop-Transcript
I wrote the following function for logging in a little script I made a while back, feel free to rip it up and abuse it!
function Write-Log {
param(
[Parameter(ValueFromPipeline =$true,
ValueFromPipelineByPropertyName=$true,
Position=0
)]
[string[]]
$Message,
[string]
$Level = "Informational"
)
if (!$loglocation)
{
write-error -Message 'No $LogLocation set.'
break
}
$logarchive = split-path $loglocation
$logarchive = $logarchive + "\Archive.log"
$truedate = get-date -f yyyy-MM-dd_HH:MM:ss
if (test-path $loglocation)
{
if ((get-item $loglocation).length -ge 5242880)
{
"$truedate : Log exceeds 5MB, archiving and creating new log." | out-file -append -filepath $loglocation
if (test-path $logArchive)
{
"$truedate : Log archive already exists, removing this log first before archiving current log." | out-file -append -filepath $loglocation
get-item $logArchive | remove-item
}
get-childitem $loglocation | rename-item -NewName {"Archive.log"}
"$truedate : New log created, old log archived." | out-file -append -filepath $loglocation
}
}
"$truedate : $level - $message" | out-file -append -filepath $loglocation
}
You just need to set a variable in your script called $loglocation to the file path you want the log to save too, i.e. "C:\MyLogs" etc. Then when you want to log something out to it, just use write-log -Message "Whatever you are logging".

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
}