why doesnt tee-object work with NoNewline? - powershell

i have the following function in my script
function Write-Host($object)
{
if($global:LogFile -eq $null)
{
$global:LogFile = $logFile
}
$object | tee $global:LogFile -Append
}
referencing this post: https://stackoverflow.com/a/25847258/8397835
I am trying specifically this part here:
$job = Start-Job -ScriptBlock { Start-Sleep -Seconds 10 }
while (($job.State -eq "Running") -and ($job.State -ne "NotStarted"))
{
Write-Host ([char]9632) -NoNewLine
Start-Sleep -Seconds 1
}
apparently, with tee, nonewline appears to be ignored...and without tee, i am getting the characters to display on one line as i am seeking
with tee:
without tee
I think i know whats happening. since write-host is being converted to tee, any switches are ignored, be it color or in this case, nonewline. How can i make nonewline work with tee?

After our chat I understand what you're trying to do. You want to write yourself a custom progress bar that both writes to a log file as well as to the console without line breaks in either. For that you can write a function that will accomplish it, but I do recommend picking a new name that doesn't conflict with an existing cmdlet. I'll use Write-MyProgress.
Function Write-MyProgress{
[cmdletbinding()]
Param(
[parameter(valuefrompipeline=$true)]$message,
[switch]$NoNewLine
)
if($global:LogFile -eq $null)
{
$global:LogFile = $logFile
}
Add-Content -Value $message -Path $LogFile -NoNewline:$NoNewLine
Write-Host $Message -NoNewLine:$NoNewLine
}
You could then call it explicitly:
Write-MyProgress ([char]9632) -NoNewLine
or pipe things to it:
[char]9632 | Write-MyProgress -NoNewLine
Or, if you don't want to use a function, you could just do it all with native cmdlets like in this example:
1..10 | ForEach-Object -Process {
[char]9632 | Add-Content $LogFile -NoNewLine -PassThru | Write-Host -NoNewLine
start-sleep -Sec 1
} -End {Add-Content -Value '' -Path $LogFile}
(Note that I add '' to the log file at the end, so the log file gets a new line after the progress bar is done)

Related

Command Start-Sleep -milliseconds 50

Playing off of someone elses script (i know nothing) is there a way to apply this sleep to a command such as "dir"?
Goal: Display DIR output in typewriter effect.
$string = "The quick brown fox jumped over the lazy dog."
$string -split '' |
ForEach-Object{
Write-Host $_ -nonew
Start-Sleep -milliseconds 50
}
It sounds like you want all the output not just the filepath or names. I offer two versions of the same concept
Multiple foreach loops
Get-ChildItem * | Out-String -Stream | ForEach-Object {
[environment]::NewLine
$_.trimend().tochararray()| ForEach-Object{
Write-Host $_ -NoNewline
Start-Sleep -milliseconds 25
}
}
Using switch statement
Switch (Get-ChildItem * | Out-String -Stream)
{
default {"`n"; $_.trimend().tochararray()|
ForEach-Object{
Write-Host $_ -NoNewline
Start-Sleep -milliseconds 25
}
}
}
Get-Item * | ForEach-Object { Write-Output $_; Start-Sleep -milliseconds 50 }
Implementation with dir (Get-Item in Powershell)
Take each line from the output ($_) wait for 50 milliseconds and print the line.
Modified your script.
This will print out each letter separately.
[string[]]$strings = (Get-ChildItem).FullName
$strings | foreach {
$_ -split '' |
ForEach-Object{
Write-Host $_ -nonew
Start-Sleep -milliseconds 1
}
Write-Host ""
}
Does this answer your question?

Can I have two "tail"s in one logfile within a while loop that one condition does one thing and the other does something else?

I have a script that is supposed to look at a logfile that starts filling with several hundred lines. While the log is filling, I need to look for two different conditions at the same time. If I find "Completed Successfully" then it will break the while loop and continue with the rest of the script. If it finds "Error" in the script, it will restart the server. Unfortunately this isn't working for me. It just sits until the timeout of the stopwatch. I'm not quite sure what is wrong here. Is there a better way to do this?
$Comp="ServerA.domain"
$Logfile="\\$Comp\Logs\logfile1.txt"
$pattern1 = "Completed Successfully"
$Pattern2 = "Error"
$timeout = new-timespan -Minutes 5
$stopwatch = [diagnostics.stopwatch]::StartNew()
while ($stopwatch.elapsed -lt $timeout){
try{
$logContent2 = Get-Content -Path $Logfile -Tail 1000 | Select-String -Pattern $Pattern2 | Measure-Object | Select-Object -ExpandProperty Count
$logContent1 = Get-Content -Path $Logfile -Tail 1000 | select-string -pattern $pattern1 | Measure-Object | Select-Object -ExpandProperty Count
If ($logContent1 -gt 0) {
Write-Host -Object "Compiled Successfully on $Comp"
#break;
}
ElseIf ($logContent2 -gt 0) {
Write-Host -Object "Error Found on $Comp! Restart required. Rebooting..."
Restart-Computer -ComputerName $Comp -Wait -For PowerShell -Force
}
}
Catch{
$Error[0] > \\$Comp\Logs\ErrorLog.txt
}
}
If ($stopwatch.elapsed -ge $timeout){
Write-Error -Message "$pattern1 did not appear" -ErrorAction Stop
exit;
}
Yes! I accidentally placed a break in the wrong place. This is resolved. The above code works but you need to break the loop (I had a break in my code in the wrong place) and I don't include the break in the code in this example.

Combine output from two Jobs

I have a Windows command line utility which outputs text to standard output, as well as logging to a file. The two outputs complement each other, and so I want to be able combine both streams. The standard output will remain untouched. However, I aim to take chunks of the log file, process them, and also send it to standard output.
My first attempt was to run:
[HashTable] $queue = #{};
$roboDealerJob = Start-Job -ScriptBlock {
Param(
[string] $outputFile,
[HashTable] $queue
)
& "utility.exe" $outputFile |
ForEach-Object
{
$queue.Add($_, $_);
}
} -Name "MyUtility" -ArgumentList $outputFile,$queue;
While (!(Test-Path $logFile))
{
Start-Sleep -Milliseconds 100;
}
Get-Content -Path $logFile -Tail 1 -Wait |
ForEach-Object {
Receive-Job $roboDealerJob
ForEach ($item in $queue.Values)
{
Write-Host $item;
$queue.Remove($item);
}
Write-Host $_;
}
However, the job doesn't seem to run.
Any suggestions on how to do this?

How to log output to file

powershell newb here. I am having some difficulty trying log my output to a file. I have tried two tactics, both of which do not work for me. The first is using the Start/Stop-Transcript cmdlet. This works great in testing on my local machine, but doesn't seem to work at all in a script that I deploy to workstations.
$path1 = Test-Path ($env:ProgramFiles + "\Sophos\Sophos Anti-Virus\SavService.exe")
$path2 = Test-Path (${env:ProgramFiles(x86)} + "\Sophos\Sophos Anti-Virus\SavService.exe")
$shareloc = '\\SERVER1\NETLOGON\SophosPackages\SophosInstall_wFW_Silent.exe'
$logpath = '\\SERVER1\NETLOGON\si_sophos_log.txt'
if (($path1 -eq $true) -or ($path2 -eq $true)) {} ELSE {
& $shareloc
Start-Transcript -Append -Path $logpath | Out-Null
Write-Output ""
Get-Date
Write-Output "Sophos has been installed on `"$env:COMPUTERNAME`""
Write-Output ""
Stop-Transcript
}
The way I would prefer to do it, is using: | Out-File -Append -FilePath $logpath
I think this would be the preferred method because it would catch any error that might occur in the log, as apposed to Start-Transcript. When I try to use this method however, I get an error at the pipeline "An empty pipeline element is not allowed."
$path1 = Test-Path ($env:ProgramFiles + "\Sophos\Sophos Anti-Virus\SavService.exe")
$path2 = Test-Path (${env:ProgramFiles(x86)} + "\Sophos\Sophos Anti-Virus\SavService.exe")
$shareloc = '\\SERVER1\NETLOGON\SophosPackages\SophosInstall_wFW_Silent.exe'
$logpath = '\\SERVER1\NETLOGON\si_sophos_log.txt'
if (($path1 -eq $true) -or ($path2 -eq $true)) {} ELSE {
& $shareloc
Write-Output ""
Get-Date
Write-Output "Sophos has been installed on `"$env:COMPUTERNAME`""
Write-Output ""
} | Out-File -Append -FilePath $logpath
Thank you in advance for any assistance!
If you write the following :
if ($true) {Write-Output "titi"} else {Write-Output "toto"} | Out-File -Append c:\temp\titi
You will get the same error, because the if condition is not evaluated when you pipe.
You can try to force ti evaluate it
$(if ($true) {Write-Output "titi"} else {Write-Output "toto"}) | Out-File -Append c:\temp\titi
When the if condition evaluates as true, the empty scriptblock gets piped to Out-File which causes your error. i.e. the following throws the error you specified:
if($true) { } else { Write-Output "Something" } | Out-File -Append -FilePath C:\temp\myfile.txt

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
}