Get-WindowsUpdateLog stream re-direction - powershell

Has anyone noticed how the Get-WindowsUpdateLog cmdlet cannot be redirected to any streams?
Furthermore, storing the output into a variable, piping it, or any type of re-direction leads to the cmdlet to only be executed.
Any help redirecting/silencing the output of this command would be appreciated.
What I've tried:
Get-WindowsUpdateLog | Out-Null
Get-WindowsUpdateLog > $null
$sink = Get-WindowsUpdateLog

Everything I could find failed to suppress the output of the CmdLet Get-WindowsUpdateLog. As you say, the displayed information in the console is not properly following the output streams as we know them in PowerShell.
The only workaround I found is using Jobs:
$Job = Start-Job -ScriptBlock {Get-WindowsUpdateLog}
$Job | Wait-Job | Remove-Job
This way all output is handled within the job and we don't retrieve the result. It's also unnecessary to retrieve it as the result is simply a text file placed in the -Path parameter.

As an addendum to DarkLite's answer, we can also utilise the following code to check if Get-WindowsUpdateLog cmdlet worked properly :
# Generate a unique filename.
$o_filename = "C:\temp\" + "WindowsUpdateLog" + "_" + ( Get-Date -UFormat "%H_%M_%S" ) + ".txt"
$o_job = Start-Job -ScriptBlock { Get-WindowsUpdateLog -LogPath "$( $args[0] )" } `
-ArgumentList $o_filename -ErrorAction SilentlyContinue
Start-Sleep -Seconds 5
$o_job | Remove-Job -ErrorAction SilentlyContinue
if( ! ( Test-Path $o_filename ) ) {
# Return an exception
}
else {
# Do some work on the generated log file
}

I think the lesson here is don't use Out-Default in a script. https://keithga.wordpress.com/2018/04/03/out-default-considered-harmful/ I don't see that command in powershell 6. But it is in powershell 7!

Related

Powershell script to determine when a program is quit

I have two scripts I would like to combine, but the second script can't begin until a program (Photoshop) is closed. Script one ends by starting a photoshop script with Invoke-Item. Once the Photoshop script is complete PhotoShop closes. The second code archives the raw files with a simple Move-Item. With PowerShell, how can I know when PhotoShop is closed and begin my Move-Item?
I have spent some time researching this to see what documentation there is, but either I am asking my questions poorly or it is an obscure enough I can't find any leads to begin off of.
# Script One
ii "E:\resizerScript.jsx"
#Something to determine when PhotoShop is closed and begin the next bit of code.
# Script Two
Move-Item -path "E:\Staged\*" -Destination "E:\Archived"
I'm very new to coding and what I have is cobbled together from other articles. If anything is too unclear I would be happy to elaborate. Thanks in advance for any help or direction.
You can use Wait-Process,
Invoke-Item "E:\resizerScript.jsx"
Wait-Process photoshop
Move-Item -Path "E:\Staged\*" -Destination "E:\Archived"
but I recommend using Start-Process -Wait to start Photoshop.
$photoshopPath = "C:\...\Photoshop.exe"
Start-Process $photoshopPath "E:\resizerScript.jsx" -Wait
Move-Item -Path "E:\Staged\*" -Destination "E:\Archived"
If you want to set the timeout:
Start-Process $photoshopPath "E:\resizerScript.jsx" -PassThru |
Wait-Process -Timeout (15 * 60) -ErrorAction Stop
First, you need to find photoshop's process name. Open powershell and run
Get-Process | Select-Object -Property ProcessName
Then use the following (you can customize it according to your needs of course, I've tested using Outlook)
param(
[string]$procName = "Outlook",
[int]$timeout = 90, ## seconds
[int]$retryInterval = 1 ## seconds
)
$isProcActive = $true
$timer = [Diagnostics.Stopwatch]::StartNew()
# to check the process' name:
# Get-Process | Select-Object -Property ProcessName
while (($timer.Elapsed.TotalSeconds -lt $timeout) -and ($isProcActive)) {
$procId = (Get-Process | Where-Object -Property ProcessName -EQ $procName).Id
if ([string]::IsNullOrEmpty($procId))
{
Write-Host "$procName is finished"
$isProcActive = $false
}
}
$timer.Stop()
if ($isProcActive)
{
Write-Host "$procName did not finish on time, aborting operation..."
# maybe you want to kill it?
# Stop-Process -Name $procName
exit
}
# do whatever
[UPDATE] if you need to put this inside another script, you need to omit the param since this must be the 1st thing in a script. So it would look like:
# start of script
$procName = "Outlook"
$timeout = 90 ## seconds
$retryInterval = 1 ## seconds
$isProcActive = $true
# etc etc
Hope this helps,
Jim

Hyper-V Powershell checkpoint creation/deletion is not synchronous. Is there a better option?

I've found myself having to write wrappers around powershell's Remove-VMSnapshot and Checkpoint-VM. The docs make no mention of it, but based on the Write-Host in both of the code snippets below executing, checkpoints are not fully deleted/created after the MS provided cmdlets. I hit this when trying to restore to a checkpoint by name immediately after creating it yielded an error.
Has anyone else encountered this? Thoughts on better ways to handle it? Tricks to prevent any of my code from calling the MS cmdlets directly?
function Remove-VMSnapshots-Sync
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)][object]$VM,
[Parameter(Mandatory=$True)][string]$CheckpointName,
)
$matchingSnapshots = #(Get-VMSnapshot $VM | Where-Object {$_.Name -eq $CheckpointName})
$matchingSnapshots | Remove-VMSnapshot
do
{
$matchingSnapshots = #(Get-VMSnapshot $VM | Where-Object {$_.Name -eq $CheckpointName})
$stillThere = $matchingSnapshots.length -gt 0
if ($stillThere)
{
Write-Host 'sleeping to try to let snapshot disappear'
start-sleep -s 1
}
} while ($stillThere)
}
function Checkpoint-VM-Sync
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)][object]$VM,
[Parameter(Mandatory=$True)][string]$CheckpointName
)
$checkpoint = Checkpoint-VM -VM $VM -SnapshotName $CheckpointName -PassThru
$checkpoint | Write-Host
while (-not (#(Get-VMSnapshot $VM | Select -ExpandProperty Id)).Contains($checkpoint.Id))
{
Write-Host 'waiting for checkpoint to be in list'
Get-VMSnapshot $VM | Write-Host
start-sleep -s 1
}
}
Had a similar issue, see the answer in Can I override a Powershell native cmdlet ... it shows you how easily it is to override commands.
You need to add it into your profile (only for you), or add it to the script (for every one that runs the script), it depends on your situation.

How can I pipe from Get-Content -Wait?

I would like to pipe the output of Get-Content $file -Wait to a custom PowerShell script. The script looks like this.
$lines = ($input | Out-String) -replace "`r", "" -split "`n"
foreach ($line in $lines) {
#Process $line
Write-Host $line
}
Basically the idea is to take the input, format it nicely and then process the output before it gets printed to the console.
The problem is nothing is getting sent to my script when I call it like cat $file -Wait | MyScript. If I do cat $file -Wait or cat $file | MyScript, everything works as expected. But combining the pipe and the wait parameter doesn't work.
Is there some syntax I need to use to allow processing the -Wait parameter? I tried using Out-String -Stream, but that doesn't work either.
The problem is with $input.
If you do this :
Get-Content $file -Wait | Get-Member -InputObject $input
Or
Get-Content $file -Wait | Get-Member -InputObject $_
You will get :
Get-Member : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
If Get-Member is unable to read the object going through the pipeline, you know that something is very wrong with the object (or the pipelining).
Let's try piping $input to Out-String, like you are doing in your script :
Get-Content $file -Wait | Out-String $input
You will get :
Out-String : A positional parameter cannot be found that accepts argument 'System.Collections.ArrayList+ArrayListEnumeratorSimple'.
At line:1 char:52
+ get-content '.\netstat anob.txt' -wait | Out-String <<<< $input
+ CategoryInfo : InvalidArgument: (:) [Out-String], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.OutStringCommand
So, indeed, "Get-Content" -Wait gives you a weird kind of object : a System.Collections.ArrayList+ArrayListEnumeratorSimple .
It looks like it's the result of the GetEnumerator() method from a System.Collections.ArrayList object, or something like that.
Given the fact that Get-Member or even "Get-Member -Force" is unable to read this kind of "Object", from Powershell's point of view, it's not a object.
The workaround would be to drop the -Wait parameter from Get-Content and find another way of achieving what you want, possibly by running Get-Content and then, running "Get-Content -Tail 1" several times in a loop.
This is possible if your script accepts pipeline input. You can see it as you have mentioned when you pipe to other cmdlets like Select-String. For example defining script.ps1 as:
process { Write-Host "line: $input" }
Then running
1..200 | foreach { add-content -Path test.txt -Value "$_"; start-sleep 1 }
in one PowerShell session and
gc test.txt -wait | .\script.ps1
in another, you can see that each line is piped to the script.
I don't see any way to do what you are asking. -Wait initiates a loop that never ends, the only way to stop is to manually kill it. Since it will always be stuck inside the loop anything you try to do after initiating the loop is never going to process.
The problem is in this line:
Write-Host $line
You should use Write-Output instead. Write-Output sends objects to pipeline, Write-Host directly to host (console).

Pipe all Write-Output to the same Out-File in PowerShell

As the title suggests, how do you make it so all of the Write-Outputs - no matter where they appear - automatically append to your defined log file? That way the script will be nicer to read and it removes a tiny bit of work!
Little example below, id like to see none of the "| Out-File" if possible, yet have them still output to that file!
$Author = 'Max'
$Time = Get-Date -Format "HH:mm:ss.fff"
$Title = "Illegal Software Removal"
$LogName = "Illegal_Remove_$($env:COMPUTERNAME).log"
$Log = "C:\Windows\Logs\Software" + "\" + $LogName
$RemoteLog = "\\Server\Adobe Illegal Software Removal"
Set-PSBreakpoint -Variable Time -Mode Read -Action { $global:Time = Get-Date -format "HH:mm:ss.fff" } | Out-Null
If((Test-Path $Log) -eq $False){ New-Item $Log -ItemType "File" -Force | Out-Null }
Else { $Null }
"[$Time][Startup] $Title : Created by $Author" | Out-File $Log -Append
"[$Time][Startup] Configuring initial variables required before run..." | Out-File $Log -Append
EDIT: This needs to work on PS v2.0, I don't want the output to appear on screen at all only in the log. So I have the same functionality, but the script would look like so...
"[$Time][Startup] $Title : Created by $Author"
"[$Time][Startup] Configuring initial variables required before run..."
You have two options, one is to do the redirection at the point the script is invoked e.g.:
PowerShell.exe -Command "& {c:\myscript.ps1}" > c:\myscript.log
Or you can use the Start-Transcript command to record everything (except exe output) the shell sees. After the script is done call Stop-Transcript.

Start-Transcript: This host does not support transcription

I want to start a transcript on a Windows Server 2008 R2
Start-Transcript -path C:\Temp\test.txt
"Hello!"
Stop-Transcript
But PowerShell returns the following error message:
Start-Transcript : This host does not support transcription.
How it is possible to activate transcript?
Windows PowerShell v4 ISE and lower do not support transcription. You must use the command line to run the commandlet.
From PowerShell v5 Start-Transcript is supported natively in ISE.
COMPLETE ANSWER (PowerShell ISE 2.0/4.0)::
Having yet another look at this today on another server, I noticed that the latest PowerShell ISE (which also does not allow Start-Transcript) does not have an Output pane, and instead uses the new ConsolePane. Thus the function now is as follows:
Function Start-iseTranscript
{
Param(
[string]$logname = (Get-logNameFromDate -path "C:\fso" -postfix " $(hostname)" -Create)
)
$transcriptHeader = #"
**************************************
Windows PowerShell ISE Transcript Start
Start Time: $((get-date).ToString('yyyyMMddhhmmss'))
UserName: $env:username
UserDomain: $env:USERDNSDOMAIN
ComputerName: $env:COMPUTERNAME
Windows version: $((Get-WmiObject win32_operatingsystem).version)
**************************************
Transcript started. Output file is $logname
"#
$transcriptHeader >> $logname
$psISE.CurrentPowerShellTab.Output.Text >> $logname
#Keep current Prompt
if ($Global:__promptDef -eq $null)
{
$Global:__promptDef = (gci Function:Prompt).Definition
$promptDef = (gci Function:Prompt).Definition
} else
{
$promptDef = $Global:__promptDef
}
$newPromptDef = #'
if ($Host.Version.Major -eq 2)
{
if ($Global:_LastText -ne $psISE.CurrentPowerShellTab.Output.Text)
{
Compare-Object -ReferenceObject ($Global:_LastText.Split("`n")) -DifferenceObject ($psISE.CurrentPowerShellTab.Output.Text.Split("`n"))|?{$_.SideIndicator -eq "=>"}|%{
$_.InputObject.TrimEnd()}|Out-File -FilePath ($Global:_DSTranscript) -Append
$Global:_LastText = $psISE.CurrentPowerShellTab.Output.Text
}
} elseif ($Host.Version.Major -eq 4)
{
if ($Global:_LastText -ne $psISE.CurrentPowerShellTab.ConsolePane.Text)
{
Compare-Object -ReferenceObject ($Global:_LastText.Split("`n")) -DifferenceObject ($psISE.CurrentPowerShellTab.ConsolePane.Text.Split("`n"))|?{$_.SideIndicator -eq "=>"}|%{
$_.InputObject.TrimEnd()}|Out-File -FilePath ($Global:_DSTranscript) -Append
$Global:_LastText = $psISE.CurrentPowerShellTab.ConsolePane.Text
}
}
'# + $promptDef
$Global:_LastText = $psISE.CurrentPowerShellTab.Output.Text
New-Item -Path Function: -Name "Global:Prompt" -Value ([ScriptBlock]::Create($newPromptDef)) -Force|Out-Null
}
Taking over the prompt is incredibly useful for this, however keeping two copies of the Output buffer is not ideal. I've also added in TrimEnd() as PSISE 2.0 likes to append spaces to fill the entire horizontal line width. Not sure if PSISE 4.0 does this too, but it's no problem now anyway.
NEW ANSWER (PowerShell ISE 2.0)::
I have just recently returned to this problem, and there is a way of forcing every update in PowerShell ISE to log out as a command is executed. This relies on the log path being saved in a Global Variable called _DSTranscript. This variable is passed to the Start-iseTranscript function. I have then Hijacked the Prompt function to execute a compare between _LastText and the hostUI Output Text, and append the differences out to the log. It now works a treat.
Function Start-iseTranscript
{
Param(
[string]$logname = (Get-logNameFromDate -path "C:\fso" -postfix " $(hostname)" -Create)
)
$transcriptHeader = #"
**************************************
Windows PowerShell ISE Transcript Start
Start Time: $(get-date)
UserName: $env:username
UserDomain: $env:USERDNSDOMAIN
ComputerName: $env:COMPUTERNAME
Windows version: $((Get-WmiObject win32_operatingsystem).version)
**************************************
Transcript started. Output file is $logname
"#
$transcriptHeader >> $logname
$psISE.CurrentPowerShellTab.Output.Text >> $logname
#Keep current Prompt
if ($__promptDef -eq $null)
{
$__promptDef = (gci Function:Prompt).Definition
$promptDef = (gci Function:Prompt).Definition
} else
{
$promptDef = $__promptDef
}
$newPromptDef = #'
if ($global:_LastText -ne $psISE.CurrentPowerShellTab.Output.Text)
{
Compare-Object -ReferenceObject $global:_LastText.Split("`n") -DifferenceObject $psISE.CurrentPowerShellTab.Output.Text.Split("`n")|?{$_.SideIndicator -eq "=>"}|%{ $_.InputObject.TrimEnd()}|Out-File -FilePath ($Global:_DSTranscript) -Append
$global:_LastText = $psISE.CurrentPowerShellTab.Output.Text
}
'# + $promptDef
New-Item -Path Function: -Name "Global:Prompt" -Value ([ScriptBlock]::Create($newPromptDef)) -Force|Out-Null
}
ORIGINAL ANSWER::
PowerShell ISE does not natively support Transcription. There is a Scripting Guy blog about how to achieve this. Unfortunately this needs to be the last thing that is run in the script. This means that you need to remember to run it before closing the window. I wish this worked better, or there was a way to force it to run on window closure.
This is the function that produces close to the same result as the Start-Transcript feature:
Function Start-iseTranscript
{
Param(
[string]$logname = (Get-logNameFromDate -path "C:\fso" -name "log" -Create)
)
$transcriptHeader = #"
**************************************
Windows PowerShell ISE Transcript Start
Start Time: $(get-date)
UserName: $env:username
UserDomain: $env:USERDNSDOMAIN
ComputerName: $env:COMPUTERNAME
Windows version: $((Get-WmiObject win32_operatingsystem).version)
**************************************
Transcript started. Output file is $logname
"#
$transcriptHeader >> $logname
$psISE.CurrentPowerShellTab.Output.Text >> $logname
} #end function start-iseTranscript
Either accept you can't, or use a host that does support transcripts (like the console host: PowerShell.exe).
The powershell.exe will also generate this error if there is a problem writing to the log file. For example, if the log file was created by an administrator and the user doesn't have permissions to overwrite the log.
Start-Transcript : The host is not currently transcribing.
At D:\Test1.ps1:9 char:1
+ Start-Transcript -Path "$Source\logs\Test.txt"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Start-Transcript], PSInvalidOperationException
+ FullyQualifiedErrorId : InvalidOperation,Microsoft.PowerShell.Commands.StartTranscriptCommand
A good solution is to try using -Append or to make the log file unique by generating a date/time stamp.
Start-Transcript -Path "$Source\logs\Test.txt" -Append
This way a proper error message is generated.
Access to the path 'D:\Test\logs\Test.txt' is denied.
-Force has the same effect as -Append and will generate a permissions error.
Following the tip from #richard here I created a snippet that allows usage of Transaction logs where I need them (scheduled tasks), so I ended having in Windows 2008R2 the following code that can be run from powershell ISE or as a standalone script.
When run in ISE, the log information will be printed on screen
When run as a script, the log information will be saved to a file
if ($Host.Name -eq "Windows PowerShell ISE Host") {
$ISE=$true
} else {
$ISE=$false
}
if (-Not $ISE) {
$Date = Get-Date -f HHmmss_ddyyyy
Start-Transcript -Path "C:\Temp\$Date.log"
}
//////////
code here ...
//////////
if (-Not $ISE) {
Stop-Transcript
}
Tagging on to the amazing answer and work by #dwarfsoft:
if ($Host.Name -match 'ISE' -and $Host.version.Major -lt 4)
{
#Start-Transcript will not work here. Use Start-iseTranscript by #dwarfsoft above
Start-iseTranscript
}
else
{
#Start Transcript Will work here
Start-Transcript
}
In addition to the ISE (which I note the original poster LaPhi doesn't even mention), the other thing that can cause this error is if you're trying to use Start-Transcript within an Invoke-Command scriptblock. For instance if you're running the script on your client machine, and then connecting to the Windows Server 2008 R2 box via Invoke-Command so Start-Transcript outputs to the server.
When run in the local session Start-Transcript works as expected, however when using Invoke-Command the script is run within a remote session on that computer, and remote sessions have certain limitations, one of which is not supporting transcription.