Syntax error with Uninstaller using Powershell - windows

I'm trying to get my universal uninstaller working. Here is my code:
CLS
$Software = "Zoom"
$Filter = "*" + $Software + "*"
$Program = $ProgUninstall = $FileUninstaller = $FileArg = $NULL
try
{
if (Test-Path -Path "HKLM:\SOFTWARE\WOW6432Node")
{
$programs = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction Stop
}
$programs += Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction Stop
$programs += Get-ItemProperty -Path "Registry::\HKEY_USERS\*\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue
}
catch
{
Write-Error $_
break
}
foreach($Program in $Programs)
{
$ProgDisplayName = $Program.DisplayName
$ProgUninstall = $Program.UninstallString
if($ProgDisplayName -like $Filter)
{
#$Program
$aux = $ProgUninstall -split #('\.exe'),2,[System.StringSplitOptions]::None
$Uninstaller = (cmd /c echo $($aux[0].TrimStart('"').TrimStart("'") + '.exe')).Trim('"')
$UninsParams = $aux[1].TrimStart('"').TrimStart("'").Trim().split(' ',[System.StringSplitOptions]::RemoveEmptyEntries)
if($aux -notlike "param 0 = *")
{
# $UninsParams = $aux[1].TrimStart('"').TrimStart("'").Trim().split(' ',[System.StringSplitOptions]::RemoveEmptyEntries)
}
$Uninstaller
$UninsParams
# . $Uninstaller $UninsParams | Where-Object { $_ -notlike "param 0 = *" }
}
}
In my example I'm trying to get an output for Zoom. I have the Zoom client installed and the Zoom Outlook plugin installed.
Here is the output of the program:
DisplayName : Zoom Outlook Plugin
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{0B76DE11-5937-4491-A66A-617E42170AFF}
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall
PSChildName : {0B76DE11-5937-4491-A66A-617E42170AFF}
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry
You cannot call a method on a null-valued expression.
At line:34 char:9
+ $UninsParams = $aux[1].TrimStart('"').TrimStart("'").Trim().s ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
AuthorizedCDFPrefix :
Comments : Zoom
Contact : Zoom Video Communications, Inc.
DisplayVersion : 5.4.58891
HelpLink : https://support.zoom.us/home
HelpTelephone :
InstallDate : 20201119
InstallLocation :
InstallSource : C:\temp\Zoom\
ModifyPath : MsiExec.exe /X{3109C49B-F5E4-4FEC-8F6F-EC5E4626B361}
NoModify : 1
Publisher : Zoom
Readme :
Size :
EstimatedSize : 122109
UninstallString : MsiExec.exe /X{3109C49B-F5E4-4FEC-8F6F-EC5E4626B361}
URLInfoAbout : https://zoom.us
URLUpdateInfo :
VersionMajor : 5
VersionMinor : 4
WindowsInstaller : 1
Version : 84207115
Language : 1033
DisplayName : Zoom
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{3109C49B-F5E4-4FEC-8F6F-EC5E4626B36
1}
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall
PSChildName : {3109C49B-F5E4-4FEC-8F6F-EC5E4626B361}
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry
Part of the problem from what I can tell is the Zoom Outlook plugin doesn't have any uninstall strings associated with it. I'm assuming it's just part of the Zoom Client (even though it's a seperate installer). What I'm trying to do is get this code to work without throwing any errors or displaying false positives.
Here is the output of my 2 parms "$Uninstaller" and "$UninsParams"
You cannot call a method on a null-valued expression.
At line:34 char:9
+ $UninsParams = $aux[1].TrimStart('"').TrimStart("'").Trim().s ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
.exe
/X{3109C49B-F5E4-4FEC-8F6F-EC5E4626B361}
MsiExec.exe
/X{3109C49B-F5E4-4FEC-8F6F-EC5E4626B361}
Thanks in advance!

The problem here is that since the Zoom Client Plugin doesn't have an uninstall script, your -split operation evaluates to a single empty string, and $aux[1] therefore evaluates to $null, hence the error message.
You could filter out entries without a valid UninstallString:
foreach($Program in $Programs |Where-Object UninstallString -match '\.exe')
{
# ... now you don't need to worry about this not resulting in two strings
$aux = $ProgUninstall -split #('\.exe'),2,[System.StringSplitOptions]::None
}

Related

unpin apps from taskbar and start menu using powershell

In Windows 11, using PowerShell, I am trying to unpin Microsoft Store from the taskbar and ESPN & Spotify from the start menu.
function unpin_taskbar([string]$appname) {
((New-Object -Com Shell.Application).NameSpace('shell:::{4234d49b-0245-4df3-b780-3893943456e1}').Items() |
Where-Object{$_.Name -eq $appname}).Verbs() | Where-Object{$_.Name.replace('&','') -match 'Unpin from taskbar'} | ForEach-Object{$_.DoIt()}
}
function unpin_startmenu([string]$appname) {
((New-Object -Com Shell.Application).NameSpace('shell:::{4234d49b-0245-4df3-b780-3893943456e1}').Items() |
Where-Object{$_.Name -eq $appname}).Verbs() | Where-Object{$_.Name.replace('&','') -match 'Unpin from Start'} | ForEach-Object{$_.DoIt()}
}
foreach ($taskbarapp in 'Microsoft Store') {
Write-Host unpinning $taskbarapp
unpin_taskbar("$taskbarapp")
}
foreach ($startmenuapp in 'ESPN', 'Spotify') {
Write-Host unpinning $startmenuapp
unpin_startmenu("$startmenuapp")
}
Microsoft Store is unpinned successfully, but the next two fail. This is the output.
unpinning Microsoft Store
unpinning ESPN
You cannot call a method on a null-valued expression.
At C:\Users\Administrator\Desktop\repo\general\rundeck\windows\unpin_windows_apps.ps1:7 char:5
+ ((New-Object -Com Shell.Application).NameSpace('shell:::{4234d49b ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
unpinning Spotify
You cannot call a method on a null-valued expression.
At C:\Users\Administrator\Desktop\repo\general\rundeck\windows\unpin_windows_apps.ps1:7 char:5
+ ((New-Object -Com Shell.Application).NameSpace('shell:::{4234d49b ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
What I'm I doing wrong here?
are you looking at something like this by any change?
Install-Module -Name Microsoft.PowerShell.StartMenu
Import-Module Microsoft.PowerShell.StartMenu
Remove-StartAppsTaskbarPinned -AppName "Microsoft Store"
Remove-StartAppsStartMenuPinned -AppName "ESPN"
Remove-StartAppsStartMenuPinned -AppName "Spotify"
You will need to install the module "Microsoft.PowerShell.StartMenu", this is mandatory to run those commands.
You can look at the powershell gallery to find out more what this is about and how it works.

How do I download a file using windows command line without having to input a proxy password?

After looking at various stackoverflow questions, I found several ways to download a file from a command line without interaction from the user.
The only one that worked for me also works only on Windows 10 natively :
curl -sko %TEMP%\file.txt "https://some.hostname/file.txt"
But installing an external tool like wget/curl is what I want to avoid.
What didn't work for me because of proxy errors :
Command:
bitsadmin.exe /transfer "dljob" "https://some.hostname/file.txt" %TEMP%\file.txt
Error:
DISPLAY: 'dljob' TYPE: DOWNLOAD STATE: ERROR
PRIORITY: NORMAL FILES: 0 / 1 BYTES: 0 / UNKNOWN
Unable to complete transfer.
ERROR FILE: https://some.hostname/file.txt -> E:\Users\xxx\AppData\Local\Temp\file.txt
ERROR CODE: 0x80190197
ERROR CONTEXT: 0x00000005
Command:
powershell -Command "(New-Object Net.WebClient).DownloadFile('https://some.hostname/file.txt', '%TEMP%\file.txt')"
Error:
Exception calling "DownloadFile" with "2" argument(s): "The remote server returned an error: (407) Proxy Authentication Required."
At line:1 char:1
+ (New-Object Net.WebClient).DownloadFile('https://some.hostname/file.txt ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : WebException
Command:
powershell -Command "Invoke-WebRequest 'https://some.hostname/file.txt' -OutFile %TEMP%\file.txt
Error:
Invoke-WebRequest :
Authentication required
You must be authenticated to access this URL.
...
Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.17763.1007
At line:1 char:1
+ Invoke-WebRequest 'https://some.hostname/file.txt ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
This didn't work either :
powershell -Command "$client.Credentials = Get-Credential; $browser.Proxy.Credentials =[System.Net.CredentialCache]::DefaultNetworkCredentials; (New-Object Net.WebClient).DownloadFile('https://some.hostname/file.txt', 'file.txt')"
Error :
cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:
Credential
Get-Credential : Cannot process command because of one or more missing mandatory parameters: Credential.
At line:1 char:23
+ $client.Credentials = Get-Credential; $browser.Proxy.Credentials =[Sy ...
+ ~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Get-Credential], ParameterBindingException
+ FullyQualifiedErrorId : MissingMandatoryParameter,Microsoft.PowerShell.Commands.GetCredentialCommand
The property 'Credentials' cannot be found on this object. Verify that the property exists and can be set.
At line:1 char:39
+ ... Credential; $browser.Proxy.Credentials =[System.Net.CredentialCache]: ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyNotFound
Exception calling "DownloadFile" with "2" argument(s): "The remote server returned an error: (407) Proxy
Authentication Required."
At line:1 char:124
+ ... redentials; (New-Object Net.WebClient).DownloadFile('https://some.hostname ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : WebException
Refer to this question Access web using Powershell and Proxy
You can try something like that in Powershell and suppose that you have already created a folder named as C:\Test:
$url = "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"
$file = "C:\Test\" + $url.Split("/")[-1]
$wb = New-Object System.Net.WebClient
$wb.Proxy.Credentials =[System.Net.CredentialCache]::DefaultNetworkCredentials
$wb.DownloadFile($url,$file)
EDIT : 14/08/2020 #17:08
I tried this on Windows Powershell ISE and it works 5/5 :
cls
$start_time = Get-Date
$url = "https://cdn2.unrealengine.com/Fortnite%2FBoogieDown_GIF-1f2be97208316867da7d3cf5217c2486da3c2fe6.gif"
$Folder = "$Env:Temp\DownloadFolder"
# We create a SubFolder Named "DownloadFolder" in the temporary file %Temp% if it doesn't exists yet !
If ((Test-Path -Path $Folder) -eq 0) { New-Item -Path $Folder -ItemType Directory | Out-Null }
# We can get the name of the file to be downloaded from the variable $url
# $url = "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"
# In our case the FileName will be = "googlelogo_color_272x92dp.png" or
# Fortnite%2FBoogieDown_GIF-1f2be97208316867da7d3cf5217c2486da3c2fe6.gif
$file = $Folder+ "\" + $url.Split("/")[-1]
Try
{
$wb = New-Object System.Net.WebClient
$wb.Proxy.Credentials =[System.Net.CredentialCache]::DefaultNetworkCredentials
$wb.DownloadFile($url,$file)
# better use Invoke-Item $Folder instead of ii
Invoke-Item $Folder
Write-Output "Running Script Time taken is : $((Get-Date).Subtract($start_time).Milliseconds) millisecond(s)"
}
Catch
{
Write-Host "Error from $url" `n"Message: [$($_.Exception.Message)"] -ForegroundColor Red -BackgroundColor DarkBlue
}
This worked for me :
powershell -Command "[System.Net.WebRequest]::DefaultWebProxy = [System.Net.WebRequest]::GetSystemWebProxy(); [System.Net.WebRequest]::DefaultWebProxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials; (New-Object Net.WebClient).DownloadFile('https://some.hostname/file.txt', 'file.txt')"

Trying to automate filling input fields of web page a with powershell. But stuck with errors

I am trying to fill the input fields with powershell but facing these issues.
$ie = New-Object -com internetexplorer.application;
$ie.visible = $true;
$ie.navigate("https://mxtoolbox.com/blacklists.aspx");
while ($ie.Busy -eq $true) { Start-Sleep -Seconds 1; }
($ie.document.getElementsByName("ctl00$ContentPlaceHolder1$ucToolhandler$txtToolInput") |select -first 1).value ="99.99.99.999";
$ie.Document.getElementsByName("ctl00$ContentPlaceHolder1$ucToolhandler$btnAction").click()
while ($ie.Busy -eq $true) { Start-Sleep -Seconds 1; }
Output :
The property 'value' cannot be found on this object. Verify that the
property exists and can be set. At line:5 char:1
+ ($ie.document.getElementsByName("ctl00$ContentPlaceHolder1$ucToolhand
...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyNotFound Method invocation failed because [System.__ComObject] does not contain a method named
'click'. At line:6 char:1
+ $ie.Document.getElementsByName("ctl00$ContentPlaceHolder1$ucToolhandl
...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (click:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound

Put() sometimes throws exception when trying to assign drive letter to partition

I have a simple script that assigns a drive letter to any unlettered partition, like the following:
function GetNextAvailableLetter
{
#returns an unused char for drive letter assignment, or $null if none are available
}
foreach ($disk in ( get-wmiobject -class win32_volume | where-object { $_.DriveLetter -eq $null } ) )
{
$letter = GetNextAvailableLetter
if ( $letter -ne $null )
{
$disk.DriveLetter = $letter + ":"
$disk.Put()
}
}
Oddly, sometimes it'll work, and sometimes Put() throws an exception:
Exception calling "Put" with "0" argument(s): "Not supported"
I have no idea why Put() would throw.
I made a couple of empty, driveletterless drives on my computer and was able to recreate this and one other error that I think you might have neglected to mention.
Property 'DriveLetter' cannot be found on this object; make sure it exists and is settable.
At line:2 char:1
+ $disk.DriveLetter = "Q:"
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyAssignmentException
Exception calling "Put" with "0" argument(s): "Access is denied.
"
At line:3 char:1
+ $disk.Put()
+ ~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
The way to solve this would be run the variable $disk though for each loop or something along those lines. Another approach would be to check the $disk.Count ahead of time.
$disk = get-wmiobject -class win32_volume | where-object { $_.DriveLetter -eq $null }
If (($disk) -and ($disk.Count -eq 1)){
$disk.DriveLetter = "Q:"
$disk.Put()
}
The If should in theory protect you from errors when $disk is empty or returns more that one object.
According to the Scripting Guys:
The reason for this error is that the Windows PowerShell prompt is not running with Administrator rights. Unfortunately, the error that bubbles back up from WMI does not tell us that the problem is related to rights.

powershell 2.0 redirection file handle exception

I'm looking for a solution to the The OS handle's position is not what FileStream expected. Do not use a handle simultaneously in one FileStream and in Win32 code or another FileStream. exception that would also work on scripts called within the script containing "the fix".
For the purposes of this question, say that I have two scripts:
foo.ps1
# <fix>
$bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField"
$objectRef = $host.GetType().GetField( "externalHostRef", $bindingFlags ).GetValue( $host )
$bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetProperty"
$consoleHost = $objectRef.GetType().GetProperty( "Value", $bindingFlags ).GetValue( $objectRef, #() )
[void] $consoleHost.GetType().GetProperty( "IsStandardOutputRedirected", $bindingFlags ).GetValue( $consoleHost, #() )
$bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField"
$field = $consoleHost.GetType().GetField( "standardOutputWriter", $bindingFlags )
$field.SetValue( $consoleHost, [Console]::Out )
$field2 = $consoleHost.GetType().GetField( "standardErrorWriter", $bindingFlags )
$field2.SetValue( $consoleHost, [Console]::Out )
# </fix>
write-host "normal"
write-error "error"
write-host "yay"
.\bar.ps1
bar.ps1
write-host "normal"
write-error "error"
write-host "yay"
And foo.ps1 is being run like this:
powershell .\foo.ps1 > C:\temp\redirecct.log 2>&1
The expected output should be:
normal
C:\foo.ps1 : error
At line:1 char:10
+ .\foo.ps1 <<<<
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,foo.ps1
yay
normal
C:\bar.ps1 : error
At C:\foo.ps1:17 char:6
+ .\bar <<<< 2>&1
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,bar.ps1
yay
However, due to the known bug, the output is actually:
normal
C:\foo.ps1 : error
At line:1 char:10
+ .\foo.ps1 <<<<
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,foo.ps1
yay
normal
out-lineoutput : The OS handle's position is not what FileStream expected. Do not use a handle simultaneously in one FileStream and in Win3
2 code or another FileStream. This may cause data loss.
+ CategoryInfo : NotSpecified: (:) [out-lineoutput], IOException
+ FullyQualifiedErrorId : System.IO.IOException,Microsoft.PowerShell.Commands.OutLineOutputCommand
So the observed behavior is that the changes made by "the fix" aren't being inherited by the 'child' script (bar.ps1, in this case). When bar.ps1 tries to write, it crashes hard. If I don't guard against it somehow in foo.ps1, it will also crash hard. What can I do before/in calling bar.ps1 to prevent bar.ps1 from crashing when it tries to write?
Constraints:
Powershell 2.0
The script must be run as above
I can't modify bar.ps1 (and it should not crash when writing to stderr).
UPDATE
Below is a half-acceptable solution. I say half because it only prevents the 'parent' script from crashing. The 'child' script still fails hard when it tries to write. On the plus side, it can go as far as recognizing that bar failed.
foo.ps1:
function savepowershellfromitself {
$bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField"
$objectRef = $host.GetType().GetField( "externalHostRef", $bindingFlags ).GetValue( $host )
$bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetProperty"
$consoleHost = $objectRef.GetType().GetProperty( "Value", $bindingFlags ).GetValue( $objectRef, #() )
[void] $consoleHost.GetType().GetProperty( "IsStandardOutputRedirected", $bindingFlags ).GetValue( $consoleHost, #() )
$bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField"
$field = $consoleHost.GetType().GetField( "standardOutputWriter", $bindingFlags )
$field.SetValue( $consoleHost, [Console]::Out )
$field2 = $consoleHost.GetType().GetField( "standardErrorWriter", $bindingFlags )
$field2.SetValue( $consoleHost, [Console]::Out )
}
savepowershellfromitself
write-host "normal"
write-error "error"
write-host "yay"
$output = .\bar.ps1 2>&1
savepowershellfromitself
write-host "$output"
if( $errors = $output | ?{$_.gettype().Name -eq "ErrorRecord"} ){
write-host "there were errors in bar!"
}
write-error "error2"
write-host "done"
If you do this in foo.ps1 it solves the problem:
# <fix>
$bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField"
$objectRef = $host.GetType().GetField( "externalHostRef", $bindingFlags ).GetValue( $host )
$bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetProperty"
$consoleHost = $objectRef.GetType().GetProperty( "Value", $bindingFlags ).GetValue( $objectRef, #() )
[void] $consoleHost.GetType().GetProperty( "IsStandardOutputRedirected", $bindingFlags).GetValue( $consoleHost, #() )
$bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField"
$field = $consoleHost.GetType().GetField( "standardOutputWriter", $bindingFlags )
$field.SetValue( $consoleHost, [Console]::Out )
$field2 = $consoleHost.GetType().GetField( "standardErrorWriter", $bindingFlags )
$field2.SetValue( $consoleHost, [Console]::Out )
# </fix>
write-host "normal"
write-error "error"
write-host "yay"
powershell .\bar.ps1 2>&1 | more
Piping the output through more hides the fact that it is ultimately going to a file from the child instance of Powershell, bypassing the bug.
In fact, if you create a grandparent script foobar.ps1 which just runs foo.ps1:
powershell .\foo.ps1 2>&1 | more
Then you don't need "the fix" at all, and foo.ps1 can just be
write-host "normal"
write-error "error"
write-host "yay"
.\bar.ps1
because the piping solves the problem for all descendent scripts.

Resources