Show output before Read-Host - powershell

I'm having some issues getting some info to write to the console before Read-Host. Let me throw out a simplified example.
Function Add-Build {
[CmdletBinding()]
Param ([Parameter(Mandatory=$True,Position=1)][String]$Build
,[Parameter(Mandatory=$False,Position=2)][System.Nullable``1[[System.Int32]]]$VersionID
,[Parameter(Mandatory=$False,Position=3)][String]$BuildDescription
)
Write-Host -BackgroundColor DarkYellow "Adding SQL Build $($Build)"
IF ($VersionID -eq $null)
{
Get-SqlVersions | Out-String
$VersionID = Read-Host -Prompt "SELECT Version (Enter To Skip)" | % { IF ($_ -eq '') {$null} ELSE {$_}}
}
}
FUNCTION Test-Function {
$BuildID = (Get-BuildID -Build "11.0.3156.0").ToString()
}
If I call Add-Build directly then the Get-SqlVersions | Out-String output before the Read-Host. If I call Test-Function though the Get-SqlVersions no longer outputs to the console at all. Get-SqlVersions makes a SQL proc call and the output is a couple Datarows.
Is there a way to ensure the Get-SqlVersions data shows up when calling Test-Function?

Make it explicitly output to the host.
$GetSQL = Get-SqlVersions | Out-String
Write-Host $GetSQL

Could you please store Get-SqlVersions | Out-String; in a Variable and display that. I think that should work.
$versions = Get-SqlVersions | Out-String;
$versions

I know this is old, but I stumbled on it and can't help but contribute.
The problem is here:
Get-SqlVersions | Out-String
Change it to this:
Get-SqlVersions | Out-Host
I did some quick looking around, and Out-String seems to collect and prepare things for displaying. Out-Host just does it.

Related

Colorize Powershell Format-List output?

I am attempting to highlight a single output field in my Powershell commandlet with some color. Seems simple and perhaps the answer is it cannot be done in a simple fashion. If that is the case, that is fine, just wanted to see if anyone knew of any easy way to accomplish the desired output.
What I am trying to run (Using Hashtable to attempt to add color):
Get-Mailbox -PublicFolder | Sort Name | FL #{Name='Mailbox Name';Expression={write-host -NoNewline $_.Name -ForegroundColor Green}},Proh*,Issue*,MaxReceiveSize,MaxSendSize,UseDatabaseQ*,Database
In the output you will notice the write-host is putting the name of the mailbox before it's label.
Code with Hashtable
Now to contrast, if I avoid attempting to add color (Simplified code without the Hashtable):
Get-Mailbox -PublicFolder | Sort Name | FL Name,Proh*,Issue*,MaxReceiveSize,MaxSendSize,UseDatabaseQ*,Database
Everything comes out perfectly formatted.
Simplified code without Hashtable
Is there a way to get the standard FL formatting but adding color to that one output?
Responses:
To Theo, Thank you that worked! Appreciate you adding the multi color option.
To JS2010, Thank you for your suggestion. I am, unfortuntely, not getting the desired output. As stated below, I am on PS 4.0 currently(plans to upgrade soon).
To Øyvind, similar to the issue seen in JS2010s code. Here is what I am getting from your suggestion. Possibly again a versioning issue.
You could do this by first capturing the Format-List output as string in a variable, split it at the newlines and output each line yourself like this:
$result = Get-Mailbox -PublicFolder | Sort-Object Name |
Format-List Name,Proh*,Issue*,MaxReceiveSize,MaxSendSize,UseDatabaseQ*,Database |
Out-String
If you only want to color the one property:
switch -regex ($result -split '\r?\n') {
'^Name\s*:' {
$label,$value = $_ -split ':',2
Write-Host ($label + ":") -NoNewline
Write-Host $value -ForegroundColor Green
}
default { $_ }
}
Output:
If you want to be able to color more properties, create a hashtable with the property names and the colors to use:
$propertyColors = #{
Name = 'Green'
MaxSendSize = 'Cyan'
}
switch ($result -split '\r?\n') {
default {
if (![string]::IsNullOrWhiteSpace($_)) {
$label,$value = $_ -split ':',2
if ($propertyColors.ContainsKey($label.Trim())) {
Write-Host ($label + ":") -NoNewline
Write-Host $value -ForegroundColor $propertyColors[$label.Trim()]
}
else { $_ }
}
else { $_ }
}
}
Output:
js2010's tip and variable interpolation will do the trick!
$e = [char]0x1b # escape
Get-Mailbox -PublicFolder | Sort Name | FL #{Name='Mailbox Name';Expression={"$e[32m$($_.Name)$e[0m"}},Proh*,Issue*,MaxReceiveSize,MaxSendSize,UseDatabaseQ*,Database
My first attempt. If you want to go through the trouble. There's probably a powershell module that does color (Pansies?). Write-host doesn't write to standard output, and redirection takes away the color.
$e = [char]0x1b # escape
[pscustomobject]#{a = "$e[32mhi$e[0m"} | format-table
Output (pretend the 'hi' is green):
a
-
hi

PowerShell console output from Tee-Object command inside function inside IF statement

Consider following code:
Function ShowSave-Log {
Param ([Parameter(Mandatory=$true)][String] $text)
$PSDefaultParameterValues=#{'Out-File:Encoding' = 'utf8'}
$date=[string](Get-Date).ToString("yyyy/MM/dd HH:mm:ss")
Tee-Object -InputObject "$date $text" -FilePath $LOG_FILE -Append
#Write-Host "$date $text"
}
Function Is-Installed {
Param ([parameter(Mandatory=$true)][String] $app_name, [String] $app_version)
$apps = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" |
Select-Object DisplayName, DisplayVersion
$apps += Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" | Select-Object DisplayName, DisplayVersion
$apps = $apps.Where{$_.DisplayName -like "$app_name"}
if ($apps.Count -eq 0) {
ShowSave-Log "`'$app_name`' not found in the list of installed applications."
return $false
} else {
ShowSave-Log "`'$app_name`' is installed."
return $true
}
}
$LOG_FILE="$Env:TEMP\LOG.log"
if (Is-Installed "Notepad++ (64-bit x64)") {Write-Host "TRUE"}
I'd expect to see message from Tee-Object command in ShowSave-Log function, however it is never shown in terminal. I am guessing it's because it is called from 'if' statement. How can I get Tee-Object output to terminal screen ? It is saved to log file.
BTW Write-Host command correctly outputs message to terminal.
I am using PowerShell ISE, Visual Studio code and PowerShell terminal. PowerShell version 5.1
There is a common misconception about how Powershell functions return data. Actually there isn't a single return value or object as you are used to from other programming languages. Instead, there is an output stream of objects.
There are several ways to add data to the output stream, e. g.:
Write-Output $data
$data
return $data
Confusing to PS newcomers coming from other languages is the fact that return $data does not define the sole "return value" of a function. It is just a convenient way to combine Write-Output $data with an early exit from the function. Whatever data that was written to the output stream befor the return statement also contributes to the output of the function!
Analysis of the code
Tee-Object -InputObject "$date $text" -FilePath $LOG_FILE -Append
... appends the InputObject to the output stream of ShowSave-Log
ShowSave-Log "`'$app_name`' is installed."
... appends the message to the output stream of Is-Installed
return $true
... appends the value $true to the output stream of Is-Installed
Now we actually have two objects in the output stream of Is-Installed, the string message and the $true value!
if (Is-Installed "Notepad++ (64-bit x64)") {Write-Host "TRUE"}
Let me split up the if-statement to explain in detail what it does:
$temp = Is-Installed "Notepad++ (64-bit x64)"
... redirects the output stream of Is-Installed to temporary variable. As the output stream has been stored into a variable, it won't go further up in the function call chain, so it won't show up in the console anymore! That's why you don't see the message from Tee-Object.
In our case there is more than one object in the output stream, so the variable will be an array like #('... is installed', $true)
if ($temp) {Write-Host "TRUE"}
... does an implicit boolean conversion of the array $temp. A non-empty array converts to $true. So there is a bug here, because the function Is-Installed always "returns" a non-empty array. When the software is not installed, $temp would look like #('... not found ...', $false), which also converts to $true!
Proof:
$temp = Is-Installed "nothing"
$temp.GetType().Name # Prints 'Object[]'
$temp[0] # Prints '2020.12.13 12:39:37 'nothing' not found ...'
$temp[1] # Prints 'False'
if( $temp ) {'Yes'} # Prints 'Yes' !!!
How can I get Tee-Object output to terminal screen?
Don't let it write to the output stream, which should be used only for actual data to be "returned" from a function, not for log messages.
A simple way to do that is to redirect the output of Tee-Object to Write-Host, which writes to the information stream:
Tee-Object -InputObject "$date $text" -FilePath $LOG_FILE -Append | Write-Host
A more sensible way would be to redirect to the verbose stream:
Tee-Object -InputObject "$date $text" -FilePath $LOG_FILE -Append | Write-Verbose
Now the log message doesn't clutter the terminal by default. Instead, to see detailed logging the caller has to enable verbose output, e. g. by setting $VerbosePreference = 'Continue' or calling the function with -Verbose parameter:
if( Is-Installed 'foo' -Verbose ){<# do something #>}
It might be easier to understand if you think of it as
$result = Is-Installed "Notepad++ (64-bit x64)"
if ($result) {Write-Host "TRUE"}
It's pretty clear that way that the result isn't output to the console at any time.
You may also be misunderstanding how return works
ShowSave-Log "`'$app_name`' not found in the list of installed applications."
return $false
is functionally the same as
ShowSave-Log "`'$app_name`' not found in the list of installed applications."
$false
return
You'd be better of having your functions return simple PowerShell objects rather than human readable text and truth values.
function Get-InstalledApps {
param (
[parameter(Mandatory=$true)][string] $app_name,
[string] $app_version
)
$installPaths = #(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
Get-ItemProperty -Path $installPaths | Where-Object DisplayName -like $app_name
}
And leave the formatting for the user to the top level of your script.
It could be worth looking at custom types with the DefaultDisplayPropertySet property. For example:
Update-TypeData -TypeName 'InstalledApp' -DefaultDisplayPropertySet 'DisplayName', 'DisplayVersion'
function Get-InstalledApps {
param (
[parameter(Mandatory=$true)][string] $app_name,
[string] $app_version
)
$installPaths = #(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
Get-ItemProperty -Path $installPaths | Where-Object DisplayName -like $app_name | Add-Member -TypeName 'InstalledApp' -PassThru
}
Or without a custom type, this abomination of a one liner:
Get-ItemProperty -Path $installPaths | Where-Object DisplayName -like $app_name | Add-Member -MemberType MemberSet -Name PSStandardMembers -Value ([System.Management.Automation.PSMemberInfo[]](New-Object System.Management.Automation.PSPropertySet DefaultDisplayPropertySet, ([string[]]('DisplayName', 'DisplayVersion')))) -PassThru
Also worth taking a look at is the Approved Verbs for PowerShell page.

Cleanup PowerShell Command

I have been trying to re-format this command by making it cleaner but I just can't seem to get around the write-output.
Get-QARSOperation -ParentContainer 'somedomain.com/OU1/OU2' -TargetObjectType 'user' |
Where-Object {$_.Status -eq 'Completed' -and $_.Controls.ID -eq 'OperationReason'} |
ForEach-Object {Get-QARSApprovalTask -Operation $_.ID} |
ForEach-Object {
Write-OutPut ("Target: " + $_.Operation.TargetObjectInfo.DN.Replace("CN=","").Replace("cn=","").Replace("\","").Replace(",","").Replace("OU","").Split('=')[0]);
Write-OutPut ("Operation ID: "+ $_.Operation.ID);
Write-OutPut ("Approver: " + $_.CompletedBy.DN.Replace("CN=","").Replace("\","").Replace(",","").Replace("OU","").Split('=')[0]);
Write-OutPut ("StartedOn: " + $_.Created);
Write-OutPut ("Completed: " + $_.Completed);
Write-OutPut ("Comments: " + $_.CompletionReason);
Write-OutPut ("Operation Type: " + $_.Operation.Type);
Write-OutPut ""
}
Also the format when I export to csv doesn't put the data into columns. What suggestions do you have to make this script look neater?
Thank you!
As suggested in the comments the correct thing to do is use Export-Csv to generate a CSV file. As for creating an object that you want to export and making that easy to read in the code you could do something similar to what you have, and use it to create a custom object that could then be piped to Export-Csv. Also, I think your whole .Replace("CN=","").Replace("cn=","").Replace("\","").Replace(",","").Replace("OU","").Split('=')[0] can be simplified to .Split('=,')[1]. The string's .Split() method accepts multiple characters to split on, and it will split on any of the characters provided. Here's what I would suggest, you will need to update the path at the end, and may have to revert to your longer .Replace bit if mine doesn't work for you.
Get-QARSOperation -ParentContainer 'somedomain.com/OU1/OU2' -TargetObjectType 'user' |
Where-Object {$_.Status -eq 'Completed' -and $_.Controls.ID -eq 'OperationReason'} |
ForEach-Object {Get-QARSApprovalTask -Operation $_.ID} |
ForEach-Object {
[PSCustomObject][Ordered]#{
"Target" = $_.Operation.TargetObjectInfo.DN.Split('=,')[1]
"Operation ID" = $_.Operation.ID
"Approver" = $_.CompletedBy.DN.Split('=,')[1]
"StartedOn" = $_.Created
"Completed" = $_.Completed
"Comments" = $_.CompletionReason
"Operation Type" = $_.Operation.Type
}
} |
Export-Csv C:\Path\To\File.csv -NoTypeInformation
You could use a Select statement, but I think this looks cleaner for you.

Local Groups and Members

I have a requirement to report the local groups and members from a specific list of servers. I have the following script that I have pieced together from other scripts. When run the script it writes the name of the server it is querying and the server's local group names and the members of those groups. I would like to output the text to a file, but where ever I add the | Out-File command I get an error "An empty pipe element is not allowed". My secondary concern with this script is, will the method I've chosen the report the server being queried work when outputting to a file. Will you please help correct this newbies script errors please?
$server=Get-Content "C:\Powershell\Local Groups\Test.txt"
Foreach ($server in $server)
{
$computer = [ADSI]"WinNT://$server,computer"
"
"
write-host "==========================="
write-host "Server: $server"
write-host "==========================="
"
"
$computer.psbase.children | where { $_.psbase.schemaClassName -eq 'group' } | foreach {
write-host $_.name
write-host "------"
$group =[ADSI]$_.psbase.Path
$group.psbase.Invoke("Members") | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
write-host **
write-host
}
}
Thanks,
Kevin
You say that you are using Out-File and getting that error. You don't show_where_ in your code that is being called from.
Given the code you have my best guess is that you were trying something like this
Foreach ($server in $server){
# All the code in this block
} | Out-File c:\pathto.txt
I wish I had a technical reference for this interpretation but alas I have not found one (Think it has to do with older PowerShell versions). In my experience there is not standard output passed from that construct. As an aside ($server in $server) is misleading even if it works. Might I suggest this small change an let me know if that works.
$servers=Get-Content "C:\Powershell\Local Groups\Test.txt"
$servers | ForEach-Object{
$server = $_
# Rest of code inside block stays the same
} | Out-File c:\pathto.txt
If that is not your speed then I would also consider building an empty array outside the block and populate is for each loop pass.
# Declare empty array to hold results
$results = #()
Foreach ($server in $server){
# Code before this line
$results += $group.psbase.Invoke("Members") | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
# Code after this line
}
$results | Set-Content c:\pathto.txt
Worthy Note
You are mixing Console output with standard output. depending on what you want to do with the script you will not get the same output you expect. If you want the lines like write-host "Server: $server" to be in the output file then you need to use Write-Output

Colour-coding get-content results

I've got a powershell script that monitors a log file, filters out the interesting bits and then presents those bits to me as and when they are written to the file. Works wonderfully. The line of interest is:
get-content "$logFile" -wait | where { select-string $searchTerm -inp $_ }
Now I want to get fancy!
I would like the font colour to change everytime a particular term is encountered. I can set the font colour easily enough, but how would you do it on-the-fly with the above statement?
Edit: Figured it out, but can't post an answer for 8 hours. Will upload it tomorrow.
If you're looking for something that provides selective color coding, then try something like this.
First, set up a helper function to select an appropriate color:
function Get-LogColor {
Param([Parameter(Position=0)]
[String]$LogEntry)
process {
if ($LogEntry.Contains("DEBUG")) {Return "Green"}
elseif ($LogEntry.Contains("WARN")) {Return "Yellow"}
elseif ($LogEntry.Contains("ERROR")) {Return "Red"}
else {Return "White"}
}
}
Then execute a line that looks like this:
gc -wait $logFile | ForEach {Write-Host -ForegroundColor (Get-LogColor $_) $_}
Try
Get-Content $logFile -Wait |
Select-String $searchTerm |
ForEach {write-host -ForegroundColor red $_.line}