Customizing PS Prompt in Windows 10 shows unexpected Result - windows

I am trying to customize my PS Prompt in Windows 10 by adding the Azure Account ID. However, it is not working as expected.
Output of Azure Account ID, results in the following:
[0.04 sec] > (Get-AzContext).Account.Id
david.maryo#jd.com
I want the my result 'Account.Id' from the above command should be displayed in my PS Prompt. But it is not displaying the necessary results. Please refer the below screenshot for information.
Screenshot of the Current PS Prompt:
Microsoft.PowerShell_profile.ps1
function prompt {
#Assign Windows Title Text
$host.ui.RawUI.WindowTitle = "Current Folder: $pwd"
#Configure current user, current folder and date outputs
$CmdPromptCurrentFolder = Split-Path -Path $pwd -Leaf
$CmdPromptUser = [Security.Principal.WindowsIdentity]::GetCurrent();
$Date = Get-Date -Format 'dddd hh:mm:ss tt'
# Test for Admin / Elevated
$IsAdmin = (New-Object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
#Calculate execution time of last cmd and convert to milliseconds, seconds or minutes
$LastCommand = Get-History -Count 1
if ($lastCommand) { $RunTime = ($lastCommand.EndExecutionTime - $lastCommand.StartExecutionTime).TotalSeconds }
if ($RunTime -ge 60) {
$ts = [timespan]::fromseconds($RunTime)
$min, $sec = ($ts.ToString("mm\:ss")).Split(":")
$ElapsedTime = -join ($min, " min ", $sec, " sec")
}
else {
$ElapsedTime = [math]::Round(($RunTime), 2)
$ElapsedTime = -join (($ElapsedTime.ToString()), " sec")
}
#Decorate the CMD Prompt
Write-Host ""
Write-Host "Azure: (Get-AzContext).Account.Id"
Write-host ($(if ($IsAdmin) { 'Elevated ' } else { '' })) -BackgroundColor DarkRed -ForegroundColor White -NoNewline
Write-Host " USER:$($CmdPromptUser.Name.split("\")[1]) " -BackgroundColor DarkBlue -ForegroundColor White -NoNewline
If ($CmdPromptCurrentFolder -like "*:*")
{Write-Host " $CmdPromptCurrentFolder " -ForegroundColor White -BackgroundColor DarkGray -NoNewline}
else {Write-Host ".\$CmdPromptCurrentFolder\ " -ForegroundColor White -BackgroundColor DarkGray -NoNewline}
Write-Host " $date " -ForegroundColor White
Write-Host "[$elapsedTime] " -NoNewline -ForegroundColor Green
return "> "
} #end prompt function

Tomalak is hinting at the problem in a comment (and shows an alternative solution), but let me spell it out:
In order:
to embed the output from an expression or command - (Get-AzContext).Account.Id, in your case - inside "..." , an expandable (double-quoted) string,
you need to enclose it in $(...), the subexpression operator:
Write-Host "Azure: $((Get-AzContext).Account.Id)"
See this answer for a comprehensive overview of PowerShell's expandable strings (string interpolation).
In general, only the following characters are metacharacters inside "..." - all others are treated as literals (which explains why you saw verbatim (Get-AzContext).Account.Id) in your prompt string):
` (backtick) PowerShell's escape character - see the conceptual about_Special_Characters help topic.
If doubled, also ": "" escapes a single " and is equivalent to `"
$, the start of a variable reference (e.g., $HOME) or subexpression ($(...), as above).
For the sake of completeness, here are alternative solutions:
# String concatenation - the expression must be enclosed in (...)
Write-Host ('Azure: ' + (Get-AzContext).Account.Id)
# The -f (string-formatting operator)
Write-Host ('Azure: {0}' -f (Get-AzContext).Account.Id)
# Tomalak's alternative, which relies on Write-Host space-concatenating
# its individual arguments.
# Note how a single expression can act as a command argument as-is.
Write-Host 'Azure:' (Get-AzContext).Account.Id

Related

How can the newline be trimmed from end of `clip` input in Powershell?

In Powershell, when I run either of the commands below:
echo "abc" | clip
echo "abc" > clip
and paste the output into an editor, then a new line has been appended:
abc
<newline>
How can input be sent to the clip command without having a trailing newline character?
(This happens in Powershell version 5.1 - extracted from $PSversionTable)
This seems to have worked:
echo "abc def" | Set-Clipboard -Value {$_.Trim()}
Hmm, if you were using Write-Host you can use the -NoNewline switch
Write-host -NoNewline "Hello World"
Write-host -NoNewline " Continuing on"

How can I add the device names that were not scanned (offline etc) by a PowerShell script

Very very much a PowerShell newbie here I wanted a script to scan devices on the network and report on Local Admins. Found one out there and made some minor modifications to meet my needs - but I have one mod I cant work out how to do. Hoping someone out there will know a simple way to do it ?
The scrip below will read in a list of device names - scan them and output a dated report for all devices that are live and on-line. If the device is not accessible I get the following error on screen but nothing in the report.
I would like when it encounters an error that it writes to the report file - something along the lines of "$computor was not accessible!"
The code I am using is
$date = Get-Date -Format o | foreach {$_ -replace ":", "."}
ECHO "Starting scan"
$Result = #()
foreach($server in (gc .\servers.txt)){
$computer = [ADSI](”WinNT://” + $server + “,computer”)
$Group = $computer.psbase.children.find(”Administrators”)
$Filename = "c:\" + "LocalAdminAudit" + $date + ".txt"
function getAdmins
{
ECHO "SEARCHING FOR DEVICE"
$members = ($Group.psbase.invoke(”Members”) | %
{$_.GetType().InvokeMember(”Adspath”, ‘GetProperty’, $null, $_, $null)}) -
replace ('WinNT://DOMAIN/' + $server + '/'), '' -replace ('WinNT://DOMAIN/',
'DOMAIN\') -replace ('WinNT://', '')
$members}
ECHO "READY TO WRITE OUTPUT"
$Result += Write-Output "SERVER: $server"
$Result += Write-Output ' '
$Result += ( getAdmins )
$Result += Write-Output '____________________________'
$Result += Write-Output ' '
ECHO "Record written"
}
# Added date run to report
$result += Write-Output "Date Reported: $date"
$Result > $Filename
Invoke-Item $Filename
# replace "DOMAIN" with the domain name.
ECHO "Scan Complete"
And the on screen error when a machine is off line or otherwise doesn't respond is
Exception calling "Find" with "1" argument(s): "The network path was not found.
"
At \server\users\User.Name\Powershell Scripts\Get-Local-AdminsV3.ps1:1
0 char:40
+ $Group = $computer.psbase.children.find <<<< (”Administrators”)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
I would like when it encounters an error that it writes to the report file - something along the lines of "$computor was not accessible!" - I am pretty sure there must be an easy way of doing this - but I cant work it out so any tips would be greatly appreciated
As Matt, mentioned in the comments. You can use a Try/Catch block inside your function to catch the error.
I also made some other changes. The most major is that I changed the function to contain all of the code necessary to get the local administrator group. Then the loop just calls the function once per computer with the computer name. This function is then reusable.
Secondly rather than output to a text file, I changed to outputting to a CSV as is a more structured format that can be used better later.
Also rather than relying on writing to the console host, I used Write-Progress to report the progress of the loop.
$Servers = Get-Content .\servers.txt
$ExportFileName = "c:\LocalAdminAudit$date.csv"
function Get-LocalAdministrator {
[cmdletbinding()]
Param(
$ComputerName
)
$Group = [ADSI]("WinNT://$computername/Administrators,group")
try {
$Group.Invoke("Members") | ForEach-Object {
$User = ($_.GetType().InvokeMember("Adspath", 'GetProperty', $null, $_, $null) -split '/')[-2,-1] -join '\'
[PSCustomObject]#{
"User" = $User
"Server" = $ComputerName
"Date" = Get-Date -Format o | ForEach-Object {$_ -replace ":", "."}
}
}
}
catch {
[PSCustomObject]#{
"User" = "Failed to Report"
"Server" = $ComputerName
"Date" = Get-Date -Format o | ForEach-Object {$_ -replace ":", "."}
}
}
}
$LocalAdmins = foreach ($Server in $Servers) {
Write-Progress -Activity "Retrieving Local Administrators" -Status "Checking $Server" -PercentComplete (([array]::indexof($Servers,$Server)/($Server.count))*100)
Get-LocalAdministrator $Server
}
$LocalAdmins | Export-CSV $ExportFileName -NoTypeInformation
Invoke-Item $ExportFileName
Lastly, be careful of smart quotes especially when cutting and pasting between Outlook and word.

How to prevent "Program has stopped working" dialog when calling an .exe from Powershell

I am working on a PS script to generate .xml representations of a huge number of Crystal Reports (Windows 7). In this script, I create an object representing all the files that need to be parsed, then loop over them, calling an .exe on them one-by-one. Occasionally, this .exe crashes. This is fine because it's pretty rare and the reports that can't be processed can be flagged and reviewed manually. The problem is that I have thousands of .rpt files to process, and when the .exe crashes, Windows pops up a dialog asking to debug or continue.
Things I have tried in order to solve the issue:
HKEY_CURRENT_USER\Software\Microsoft\Windows\Windows Error Reporting\DebugApplications: I put my exe name here and set the value to 0 (don't debug)
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\Windows Error Reporting\DebugApplications: same as above
Set the loop that calls the exe to SilentlyContinue
Turn off error reporting as follows: Control Panel > Action Center > Change Action Center Settings > Problem Reporting Settings > Change report settings for all users > "Never Check for Solutions" > OK > OK (This only disables the "Windows can check online..." dialog)
Still, I'm getting the popup. There is another reg key, which disables the "Program has stopped working" UI entirely, but I don't want to do that because as a developer of other applications, I need to know when things crash. I just want to exclude this script or the exe it calls from showing the UI.
If I can do that, then the script can run unattended.
The .exe that misbehaves is the latest binary release from here: https://github.com/ajryan/RptToXml and seems to fail when it encounters a null byte in the report file.
Here is my code:
[xml]$MainConfigFile = Get-Content "settings.config.xml"
[xml]$SSRSConfigFile = Get-Content "ssrs.config.xml"
[xml]$CrystalConfigFile = Get-Content "crystal.config.xml"
# create settings objects from xml objects
$MainSettings = #{
OutputPath = $MainConfigFile.Settings.OutputPath
SearchString = $MainConfigFile.Settings.SearchString
ParseCrystal = $MainConfigFile.Settings.ParseCrystal
ParseSSRS = $MainConfigFile.Settings.ParseSSRS
}
$CrystalSettings = #{
InputFolderPath = $CrystalConfigFile.CrystalSettings.InputFolderPath
ContinueOnError = $CrystalConfigFile.CrystalSettings.ContinueOnError
}
$RsSettings = #{
ReportServerUri = $SSRSConfigFile.RsSettings.ReportServerUri
RsVersion = $SSRSConfigFile.RsSettings.RsVersion
}
Clear
Write-Host "Ensure these settings are correct before you proceed:" -ForegroundColor Yellow
Write-Host ""
Write-Host "Main Settings" -ForegroundColor Green
$MainSettings
Write-Host ""
Write-Host "Crystal Settings" -ForegroundColor Green
$CrystalSettings
Write-Host ""
Write-Host "SSRS Settings" -ForegroundColor Green
$RsSettings
Write-Host ""
# user must confirm
[string]$SettingsOK=(Read-Host "[Y] to proceed, [N] to quit:")
if ($SettingsOK -ne "Y") {exit}
Write-Host ""
Write-Host "______________________________________"
Clear
# is the output path syntax valid?
if (!(Test-Path -Path $MainSettings.OutputPath -IsValid)) {
Write-Host -ForegroundColor Green "Output path syntax is invalid:" $MainSettings.OutputPath
exit
} else {
Write-Host -ForegroundColor Green "Output path syntax is correct:" $MainSettings.OutputPath
}
# does the output path exist?
if (!(Test-Path -Path $MainSettings.OutputPath)) {
Write-Host -ForegroundColor Yellow "Output path does not exist:" $MainSettings.OutputPath
[string]$CreateOutputPathOK=(Read-Host "[Y] to create the directory, [N] to quit.")
if ($CreateOutputPathOK -ne "Y") {exit} else {New-Item -Path $MainSettings.OutputPath -ItemType Directory}
} else {
Write-Host -ForegroundColor Green "Output path already exists:" $MainSettings.OutputPath
}
Write-Host ""
Write-Host "______________________________________"
Clear
# get all .rpt files in the input folder, recursively
$CrystalFiles=Get-ChildItem -Path $CrystalSettings.InputFolderPath -Include "*.rpt" -Recurse
Write-Host ""
# count files first and ask the user if they want to see the output, otherwise proceed
Write-Host -ForegroundColor Yellow $CrystalFiles.Count ".rpt files were found in" $CrystalSettings.InputFolderPath
[string]$ShowFilesOK=(Read-Host "[Enter] to proceed, [Y] to view the list of files in the directory, [N] to quit.")
if ($ShowFilesOK -eq "Y") {
Clear
# loop through the collection of files and display the file path of each one
$CrystalFiles | ForEach-Object -Process {$_.FullName.TrimStart($CrystalSettings.InputFolderPath)}
Write-Host "______________________________________"
# user must confirm
Write-Host ""
Write-Host -ForegroundColor Yellow "The above .rpt files were found in" $CrystalSettings.InputFolderPath
} elseif ($ShowFilesOK -eq "N") {exit}
Write-Host ""
[string]$ProcessingOK=(Read-Host "[Y] to proceed with .rpt file processing, [N] to quit:")
if ($ProcessingOK -ne "Y") {exit}
Write-Host ""
Write-Host "______________________________________"
Clear
# create a dir inside the output path to hold this run's output
Write-Host -ForegroundColor Green "Creating folder to hold this run's output..."
$RunDir = (New-Item -Path $MainSettings.OutputPath -Name "$(Get-Date -f yyyy-mm-dd__hh_mm_ss)" -ItemType Directory)
$RunDir.FullName
# use .NET ArrayList because immutable PS arrays are very slow
$Success = New-Object System.Collections.ArrayList
$Failure = New-Object System.Collections.ArrayList
#loop through the collection again, this time processing each file and dumping the output to the output dir
$CrystalFiles | ForEach-Object -ErrorAction SilentlyContinue -Process {
$RelativePathName = $_.FullName.TrimStart($CrystalSettings.InputFolderPath)
$XmlFileName = "$RunDir\$RelativePathName.xml"
# force-create the file to ensure the parent folder exists, otherwise RptToXML will crash trying to write the file
New-Item -Path $XmlFileName -Force
# then simply delete the empty file
Remove-Item -Path $XmlFileName
Write-Host -ForegroundColor Green "Processing file" $RelativePathName
CMD /c .\RptToXML\RptToXml.exe $_.FullName $RunDir\$($_.FullName.TrimStart($CrystalSettings.InputFolderPath)).xml
if ($LASTEXITCODE -eq 0) {
Write-Host "Success" $Success.Add($RelativePathName)} else {Write-Host "Failure" $Failure.Add($RelativePathName)}
}
$Success | Export-CSV "$RunDir\CrystalSuccess.txt"
$Failure | Export-CSV "$RunDir\CrystalFailure.txt"
I hate to do this, answering my own question, but I've found a workaround for now.
Before the loop:
# Disable WER temporarily
Set-ItemProperty "HKCU:\Software\Microsoft\Windows\Windows Error Reporting" -Name DontShowUI -Value 1
After the loop:
# Reset the WER UI reg key
Set-ItemProperty "HKCU:\Software\Microsoft\Windows\Windows Error Reporting" -Name DontShowUI -Value 0
This will be improved by calling this script from another script:
Get the current value of the key(s) to be modified
Change them
Call the script which returns control to the caller even if it crashes
Return the reg keys to their original value

How to prevent words from being removed [duplicate]

This question already has answers here:
What is the difference between combining paths in those 2 ways?
(2 answers)
Closed 6 years ago.
I'm running into a small problem where I have to require the user to NOT put a backslash "\" in the folder name because it removes the server name when I combine $Server and $Parent... How do I prevent that from happening? I'd rather not restrict my user from adding that backslash...
Also, I've been trying to prevent c:, d:, e:, etc. drives from being used in $Parent, but even if I use -in or -contains it still allows the c:\xxxx or d:\xxxx to be entered. How do I prevent that?
# File share server name
$Server = Read-Host -prompt "Verify Server Server Name (ie ECCOFS01)"
If ([string]::IsNullOrWhiteSpace($Server)) {
Write-Host "You entered $Server which is an incorrect value: This Script is now Exiting." -Foreground "White" -Background "Red"
Exit
}
else {
Write-Host "You Entered $Server" -Foreground "Black" -Background "Yellow"
}
# Parent folder setup
$Parent = Read-Host -prompt "Enter full parent path that will contain the new folder(ie. Groups\ECCO IT) - Do NOT start with \. Please use correct spelling and capitalization (ie. Parent Folder Name). "
If ([string]::IsNullOrWhiteSpace($Parent) -or ($Parent -eq "c:") -or ($Parent -eq "d:")) {
Write-Host "You entered $Parent which is an incorrect value: This Script is now Exiting." -Foreground "White" -Background "Red"
Exit
}
else {
Write-Host "You Entered $Parent" -Foreground "Black" -Background "Yellow"
}
$ServerParentShare = "\\"+[IO.Path]::Combine($Server,$Parent)
# New Folder Name
$Name = Read-Host -prompt "Enter New Folder Name. Please use correct spelling and capitalization (ie. New Test Folder)"
If ([string]::IsNullOrWhiteSpace($Name)) {
Write-Host "You entered $Name which is an incorrect value: This Script is now Exiting." -Foreground "White" -Background "Red"
Exit
}
else {
Write-Host "You Entered $Name." -Foreground "Black" -Background "Yellow"
}
$Path = [IO.Path]::Combine($ServerParentShare,$Name)
Write-Host = "New Folder Path = $Path" -Foreground "Black" -Background "Yellow"
# Choose parent OU
$Country = Read-Host -prompt "Enter the Country OU that the Security Group will reside in (i.e. Global, Americas, Europe, Asia Pacific)"
If ([string]::IsNullOrWhiteSpace($Country)) {
Write-Host "You entered $Country which is an incorrect value: This Script is now Exiting." -Foreground "White" -Background "Red"
Exit
}
else {
Write-Host "---------------------VERIFY ENTRY---------------------" -Foreground "Black" -Background "Yellow"
Write-Host "OU = $Country, New share location = $Path" -Foreground "Black" -Background "Yellow"
}
# Option to continue or cancel the script
$Continue = Read-Host -prompt "Does this look correct? Y or N?"
If (($Continue -eq "N") -or ($Continue -eq "No")) {
Write-Host "Please Start over. This Script is now Exiting." -Foreground "White" -Background "Red"
Exit
}
else {
Write-Host "Make sure to verify all folders and and AD Groups once complete." -Foreground "Yellow" -Background "Black"
}
I have to require the user to NOT put a backslash "\" in the folder name
insert this line after line 12:
$parent = $parent -replace '^\\',''
if $parent contains a backslash at position 0, it will replace it with an empty string. if not, it has no effect.
PS C:\> '\a\b' -replace '^\\',''
a\b
PS C:\> 'a\b' -replace '^\\',''
a\b
PS C:\>
technically this doesn't prevent the user from putting a backslash in the folder name, but if he/she does, it will remove it, which has a similar effect.
I've been trying to prevent c:, d:, e:, etc. drives from being used in
$Parent, but even if I use -in or -contains it still allows
-in and -contains operate on collections, not a single object (like $parent). for $parent, you probably want to use -like or -match. you can check for a drive letter-formatted path like this:
($parent -like '?:*')
or you can just look for a colon in the path
($parent -like '*:*')
you can use either of those conditionals in a while loop, forcing the user to keep inputting until he/she inputs the format you want. or you can just exit if the input is invalid. put it all together, for example:
do{
$parent = read-host -prompt 'Enter full parent path'
$parent = $parent -replace '^\\',''
}while($parent -like '*:*')

Capture program stdout and stderr to separate variables

Is it possible to redirect stdout from an external program to a variable and stderr from external programs to another variable in one run?
For example:
$global:ERRORS = #();
$global:PROGERR = #();
function test() {
# Can we redirect errors to $PROGERR here, leaving stdout for $OUTPUT?
$OUTPUT = (& myprogram.exe 'argv[0]', 'argv[1]');
if ( $OUTPUT | select-string -Pattern "foo" ) {
# do stuff
} else {
$global:ERRORS += "test(): oh noes! 'foo' missing!";
}
}
test;
if ( #($global:ERRORS).length -gt 0 ) {
Write-Host "Script specific error occurred";
foreach ( $err in $global:ERRORS ) {
$host.ui.WriteErrorLine("err: $err");
}
} else {
Write-Host "Script ran fine!";
}
if ( #($global:PROGERR).length -gt 0 ) {
# do stuff
} else {
Write-Host "External program ran fine!";
}
A dull example however I am wondering if that is possible?
One option is to combine the output of stdout and stderr into a single stream, then filter.
Data from stdout will be strings, while stderr produces System.Management.Automation.ErrorRecord objects.
$allOutput = & myprogram.exe 2>&1
$stderr = $allOutput | ?{ $_ -is [System.Management.Automation.ErrorRecord] }
$stdout = $allOutput | ?{ $_ -isnot [System.Management.Automation.ErrorRecord] }
The easiest way to do this is to use a file for the stderr output, e.g.:
$output = & myprogram.exe 'argv[0]', 'argv[1]' 2>stderr.txt
$err = get-content stderr.txt
if ($LastExitCode -ne 0) { ... handle error ... }
I would also use $LastExitCode to check for errors from native console EXE files.
You should be using Start-Process with -RedirectStandardError -RedirectStandardOutput options. This other post has a great example of how to do this (sampled from that post below):
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "ping.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = "localhost"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode
This is also an alternative that I have used to redirect stdout and stderr of a command line while still showing the output during PowerShell execution:
$command = "myexecutable.exe my command line params"
Invoke-Expression $command -OutVariable output -ErrorVariable errors
Write-Host "STDOUT"
Write-Host $output
Write-Host "STDERR"
Write-Host $errors
It is just another possibility to supplement what was already given.
Keep in mind this may not always work depending upon how the script is invoked. I have had problems with -OutVariable and -ErrorVariable when invoked from a standard command line rather than a PowerShell command line like this:
PowerShell -File ".\FileName.ps1"
An alternative that seems to work under most circumstances is this:
$stdOutAndError = Invoke-Expression "$command 2>&1"
Unfortunately, you will lose output to the command line during execution of the script and would have to Write-Host $stdOutAndError after the command returns to make it "a part of the record" (like a part of a Jenkins batch file run). And unfortunately it doesn't separate stdout and stderr.
In case you want to get any from a PowerShell script and to pass a function name followed by any arguments you can use dot sourcing to call the function name and its parameters.
Then using part of James answer to get the $output or the $errors.
The .ps1 file is called W:\Path With Spaces\Get-Something.ps1 with a function inside named Get-It and a parameter FilePath.
Both the paths are wrapped in quotes to prevent spaces in the paths breaking the command.
$command = '. "C:\Path Spaces\Get-Something.ps1"; Get-It -FilePath "W:\Apps\settings.json"'
Invoke-Expression $command -OutVariable output -ErrorVariable errors | Out-Null
# This will get its output.
$output
# This will output the errors.
$errors
Copied from my answer on how to capture both output and verbose information in different variables.
Using Where-Object(The alias is symbol ?) is an obvious method, but it's a bit too cumbersome. It needs a lot of code.
In this way, it will not only take longer time, but also increase the probability of error.
In fact, there is a more concise method that separate different streams to different variable in PowerShell(it came to me by accident).
# First, declare a method that outputs both streams at the same time.
function thisFunc {
[cmdletbinding()]
param()
Write-Output 'Output'
Write-Verbose 'Verbose'
}
# The separation is done in a single statement.Our goal has been achieved.
$VerboseStream = (thisFunc -Verbose | Tee-Object -Variable 'String' | Out-Null) 4>&1
Then we verify the contents of these two variables
$VerboseStream.getType().FullName
$String.getType().FullName
The following information should appear on the console:
PS> System.Management.Automation.VerboseRecord
System.String
'4>&1' means to redirect the verboseStream to the success stream, which can then be saved to a variable, of course you can change this number to any number between 2 and 5.
Separately, preserving formatting
cls
function GetAnsVal {
param([Parameter(Mandatory=$true, ValueFromPipeline=$true)][System.Object[]][AllowEmptyString()]$Output,
[Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$firstEncNew="UTF-8",
[Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$secondEncNew="CP866"
)
function ConvertTo-Encoding ([string]$From, [string]$To){#"UTF-8" "CP866" "ASCII" "windows-1251"
Begin{
$encFrom = [System.Text.Encoding]::GetEncoding($from)
$encTo = [System.Text.Encoding]::GetEncoding($to)
}
Process{
$Text=($_).ToString()
$bytes = $encTo.GetBytes($Text)
$bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes)
$encTo.GetString($bytes)
}
}
$all = New-Object System.Collections.Generic.List[System.Object];
$exception = New-Object System.Collections.Generic.List[System.Object];
$stderr = New-Object System.Collections.Generic.List[System.Object];
$stdout = New-Object System.Collections.Generic.List[System.Object]
$i = 0;$Output | % {
if ($_ -ne $null){
if ($_.GetType().FullName -ne 'System.Management.Automation.ErrorRecord'){
if ($_.Exception.message -ne $null){$Temp=$_.Exception.message | ConvertTo-Encoding $firstEncNew $secondEncNew;$all.Add($Temp);$exception.Add($Temp)}
elseif ($_ -ne $null){$Temp=$_ | ConvertTo-Encoding $firstEncNew $secondEncNew;$all.Add($Temp);$stdout.Add($Temp)}
} else {
#if (MyNonTerminatingError.Exception is AccessDeniedException)
$Temp=$_.Exception.message | ConvertTo-Encoding $firstEncNew $secondEncNew;
$all.Add($Temp);$stderr.Add($Temp)
}
}
$i++
}
[hashtable]$return = #{}
$return.Meta0=$all;$return.Meta1=$exception;$return.Meta2=$stderr;$return.Meta3=$stdout;
return $return
}
Add-Type -AssemblyName System.Windows.Forms;
& C:\Windows\System32\curl.exe 'api.ipify.org/?format=plain' 2>&1 | set-variable Output;
$r = & GetAnsVal $Output
$Meta2=""
foreach ($el in $r.Meta2){
$Meta2+=$el
}
$Meta2=($Meta2 -split "[`r`n]") -join "`n"
$Meta2=($Meta2 -split "[`n]{2,}") -join "`n"
[Console]::Write("stderr:`n");
[Console]::Write($Meta2);
[Console]::Write("`n");
$Meta3=""
foreach ($el in $r.Meta3){
$Meta3+=$el
}
$Meta3=($Meta3 -split "[`r`n]") -join "`n"
$Meta3=($Meta3 -split "[`n]{2,}") -join "`n"
[Console]::Write("stdout:`n");
[Console]::Write($Meta3);
[Console]::Write("`n");

Resources