This works perfectly on Windows 10 using powershell but fails badly on Win Server 2012 R2.
I have tried many forms using redirect and -outfile but at best end-up with an empty file or broken script.
Can any one help me send the one liner below to a txt file on win server 2012 R2, I'm just not getting it
$((Get-WmiObject -Query "select Name, PercentProcessorTime from Win32_PerfFormattedData_PerfOS_Processor") | foreach-object { write-host "$($_.Name) : $($_.PercentProcessorTime)"}) *>&1 > output.txt
Write-Host, as the name implies, writes to the host, which in a console window is that window's display (screen), bypassing PowerShell's (success) output stream, the latter being what the pipeline operator (|) and the redirection operator > operate on.
In PowerShell v5+ only, Write-Host writes to the information output stream (stream number 6 - see the about_Redirection help topic; by default, that output still goes to the host) and can therefore be redirected - either via 6> or via *> - so the catch-all redirection *>&1, which redirects all streams to the success output stream (1), can indeed be used to redirect Write-Host to the success output stream, but not in earlier PowerShell versions - and Windows Server 2012 R2 shipped with PowerShell version 4.
However, in your case there is no good reason to use Write-Host to begin with: either use Write-Output - the cmdlet whose purpose is to write to the success output stream (1) - or, preferably, use PowerShell's implicit output feature, where any output (return value) not captured in a variable, piped or redirected is implicitly written to the success output stream:
# Note how the use of "$($_.Name) : $($_.PercentProcessorTime)"
# *by itself* implicitly causes it to be *output* (written to the pipeline).
Get-WmiObject -Query "select Name, PercentProcessorTime from Win32_PerfFormattedData_PerfOS_Processor" |
Foreach-Object { "$($_.Name) : $($_.PercentProcessorTime)" } > output.txt
Note the absence of $(...), the subexpression operator in the command, which isn't needed.
If the specific spacing between the columns isn't important, you can more simply write (since only 2 properties are being select, implicit Format-Table formatting is applied):
Get-WmiObject -Query "select Name, PercentProcessorTime from Win32_PerfFormattedData_PerfOS_Processor" |
Select-Object Name, PercentProcessorTime > output.txt
Note: The CIM cmdlets (e.g., Get-CimInstance) superseded the WMI cmdlets (e.g., Get-WmiObject) in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell [Core] (version 6 and above), where all future effort will go, doesn't even have them anymore. For more information, see this answer.
Try this... note that it is appending to the file, so if you run it twice, you will have two runs' worth of data
$((Get-WmiObject -Query "select Name, PercentProcessorTime from Win32_PerfFormattedData_PerfOS_Processor") | foreach-object { "$($_.Name) : $($_.PercentProcessorTime)" >> output.txt})
EDIT
OP accepted this as the answer, but it's not necessarily the best solution. For a thorough explanation and another solution, I recommend a different answer submitted for this question.
Related
I want to run a powershell script and save/redirect the result to another file.
My script is:
# Define time for report (default is 10 day)
$startDate = (get-date).AddDays(-10)
# Store successful logon events from security logs with the specified dates and workstation/IP in an array
$slogonevents = Get-Eventlog -LogName Security -after $startDate | where {$_.eventID -eq 4624 }
# Crawl through events; print all logon history with type, date/time, status, account name, computer and IP address if user logged on remotely
foreach ($e in $slogonevents){
# Logon Successful Events
# Local (Logon Type 2)
if (($e.EventID -eq 4624 ) -and ($e.ReplacementStrings[8] -eq 2)){
write-host "Type: Local Logon`tDate: "$e.TimeGenerated "`tStatus: Success`tUser: "$e.ReplacementStrings[5] "`tWorkstation: "$e.ReplacementStrings[11]
}
# Remote (Logon Type 10)
if (($e.EventID -eq 4624 ) -and ($e.ReplacementStrings[8] -eq 10)){
write-host "Type: Remote Logon`tDate: "$e.TimeGenerated "`tStatus: Success`tUser: "$e.ReplacementStrings[5] "`tWorkstation: "$e.ReplacementStrings[11] "`tIP Address: "$e.ReplacementStrings[18]
}
} >> D:\test.txt
but I get errors like that
>> : The term '>>' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and
try again.
At D:\Cyber_security\Python\Untitled1.ps1:26 char:3
+ } >> D:\test.txt
+ ~~
+ CategoryInfo : ObjectNotFound: (>>:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
why this is happening?
To address an incidental problem up front: even if you fix the redirection problem (see below), your foreach loop won't produce success-stream output, resulting in an empty file. You're using Write-Host, which is is typically the wrong tool to use, unless the intent is to write to the display only (though in PowerShell 5 and above you can capture Write-host output if you redirect it to the success output stream, e.g. with *>&1). Instead, use Write-Output (e.g. Write-Output "foo") or, preferably, implicit output (just "foo"). See also: the bottom section of this answer.
foreach is a language statement, and as such you cannot directly apply a redirection (> or >>) to it - see bottom section for an explantion.
You need to wrap it in a (by definition pipeline-based) command or expression first, for which there are two options:
Streaming option (preferred): Wrap the statement in a script block ({ ... }) and call it via &, the call operator (or, if you want the statement to run directly in the caller's scope as opposed to a child scope, as created by &, use ., the dot-sourcing operator)
& { foreach ($i in 1..2) { $i } } > test.txt
Collect-all-output-first option: Use $(...), the subexpression operator:
$(foreach ($i in 1..2) { $i }) > test.txt
Alternatively, use the ForEach-Object cmdlet, which is a command (as all named units of execution are in PowerShell), which also results in streaming processing (perhaps confusingly, a built-in alias for ForEach-Object is also named foreach, with the syntactical context deciding whether the cmdlet or the language statement is being referenced):
1..2 | ForEach-Object { $_ } > test.txt
As for what you tried:
The > (>>) operator is, in effect, an alias of the Out-File cmdlet (Out-File -Append), and therefor requires a pipeline to function.
However, language statements cannot directly be used in a pipeline, and by themselves are always self-contained statements, meaning that whatever comes after isn't considered part of the same statement.
This becomes more obvious when you replace >> with Out-File -Append:
# !! FAILS, because `| Out-File -Append test.txt` is considered
# !! a separate statement, resulting in the following error:
# !! "An empty pipe element is not allowed."
foreach ($i in 1..2) { $i } | Out-File -Append test.txt
The error message An empty pipe element is not allowed. implies that | was considered the start of a new statement.
The same happened with >>, albeit with the more obscure error message shown in your question, but you can easily reproduce it by executing >> test.txt in isolation.
Note: Unlike POSIX-compatible shells such as Bash, PowerShell does not allow you to place a redirection anywhere within a statement, and fails if it starts a statement; e.g., Get-Date >> test.txt' works fine and even Get-Date >>test.txt -Format g, but >> test.txt 'hi' does not.
Design musings:
Given that an expression can serve as the - first only - segment of a pipeline (e.g., 1..2 | Out-File -Append test.txt), it isn't obvious why a language statement cannot be used that way too.
The reason is a fundamental limitation in PowerShell's grammar:
A pipeline by itself is a statement,
but it cannot (directly) contain statements.
Hence the need to nest statements inside pipelines using the techniques shown above (& { ... } / $(..)).
Another unfortunate manifestation of this design is when you attempt to use language statements with && and ||, the PowerShell (Core) 7+ pipeline-chain operators:
Since exit and throw are language statements too, the following idiom - which would work in POSIX-combatible shells - does not work:
# Exit the script with an exit code of 1 if the Get-ChildItem call
# reports an error.
# !! FAILS, because `exit`, as a language statement, cannot be
# !! used directly in a pipeline.
Get-ChildItem NoSuchDir -ErrorAction SilentlyContinue || exit 1
Again, nesting of the statement is required, such as $(...):
# OK, due to $(...)
Get-ChildItem NoSuchDir -ErrorAction SilentlyContinue || $(exit 1)
Perhaps needless to say:
This requirement is obscure and easy to forget...
... and it is exacerbated by the fact that placing e.g. exit 1 after && or || does not cause a syntax (parse) error and only fails at runtime, and only when the condition is met.
That is, you may not notice the problem until the LHS command actually reports an error.
Additionally, the error message you get when it does fail can be confusing: The term 'exit' is not recognized as a name of a cmdlet, function, script file, or executable program. This is because exit in this context is then interpreted as the name of a command (such as a function or external program) rather than as a language statement.
Environment:
Windows Server 2016
Windows 10 Pro
PowerShell 5.1
$myVariable is empty, I think and I'm expecting there to be a string value.
Invoke-Command -ComputerName WKSP000D1E3F -Credential $creds -ScriptBlock {
sqlcmd -E -Q "select top 1 FirstName from customers" -d database1 -S "(localdb)\ProjectsV13" | Tee-Object -Variable myVariable
}
Write-Host $myVariable
Cpt.Whale has provided the crucial pointer in a comment: you fundamentally cannot set local variables from a script block being executed remotely (via Invoke-Command -ComputerName) - you must use output from the script block to communicate data back to the caller.
While you could apply Tee-Object locally instead (Invoke-Command ... | Tee-Object), there's a simpler solution, which works with all cmdlets, including cmdlet-like (advanced) functions and scripts:
Use the common -OutVariable (-ov) parameter to capture a cmdlet's output in a self-chosen variable while passing that output through:
# Note the `-OutVariable myVariable` part
# and that the variable name must be specified *without* a leading "$"
# Output is still being passed through.
Invoke-Command -OutVariable myVariable -ComputerName WKSP000D1E3F -Credential $creds -ScriptBlock {
sqlcmd -E -Q "select top 1 FirstName from customers" -d database1 -S "(localdb)\ProjectsV13"
}
# $myVariable now contains the captured content.
By contrast, if you want to capture output only, without also passing it through (to the display, by default), you can heed Santiago Squarzon's advice and simply assign the Invoke-Command call to your variable ($myVariable = Invoke-Command ...).
Notes re -OutVariable(-ov):
As shown above, and as shown with Tee-Object -Variable in your question, the name of the self-chosen target variable must be specified without a leading $, e.g. -OutVariable var, not Out-Variable $var; if you did the latter, the value of a preexisting $var variable (if defined) would be used as the variable name.
Unlike directly captured output, the target variable always receives array(-like) data, specifically, an instance of the System.Collections.ArrayList class - even if only one output object is present; e.g.:
# -> 'types: v1: String vs. v2: ArrayList'
$v1 = Write-Output -OutVariable v2 'one'
"types: v1: $($v1.GetType().Name) vs. v2: $($v2.GetType().Name)"
That is, while directly capturing output captures a single output object as-is, and multiple ones in a regular PowerShell array (of type [object[]], -OutVariable always creates an ArrayList - see GitHub issue #3154 for a discussion of this inconsistency.
With commands that do not support -OutVariable, namely simple scripts and functions as well as external programs:
To pass the output through in streaming fashion, i.e. as it becomes available, pipe to Tee-Object -Variable; e.g.:
# Passes output through as it is being emitted.
some.exe | Tee-Object -Variable myVariable
Otherwise - i.e. if it is acceptable to collect all output first, before passing it through - simply enclose an assignment statement in (...) to pass its value through - this approach performs better than Tee-Object -Variable; e.g.:
# Collects all output first, then passes it through.
($myVariable = some.exe)
Is there any sane, reliable contract that dictates whether Write-Host is supported in a given PowerShell host implementation, in a script that could be run against any reasonable host implementation?
(Assume that I understand the difference between Write-Host and Write-Output/Write-Verbose and that I definitely do want Write-Host semantics, if supported, for this specific human-readable text.)
I thought about trying to interrogate the $Host variable, or $Host.UI/$Host.UI.RawUI but the only pertinent differences I am spotting are:
in $Host.Name:
The Windows powershell.exe commandline has $Host.Name = 'ConsoleHost'
ISE has $Host.Name = 'Windows PowerShell ISE Host'
SQL Server Agent job steps have $Host.Name = 'Default Host'
I have none of the non-Windows versions installed, but I expect they are different
in $Host.UI.RawUI:
The Windows powershell.exe commandline returns values for all properties of $Host.UI.RawUI
ISE returns no value (or $null) for some properties of $Host.UI.RawUI, e.g. $Host.UI.RawUI.CursorSize
SQL Server Agent job steps return no values for all of $Host.UI.RawUI
Again, I can't check in any of the other platforms
Maintaining a list of $Host.Name values that support Write-Host seems like it would be bit of a burden, especially with PowerShell being cross-platform now. I would reasonably want the script to be able to be called from any host and just do the right thing.
Background
I have written a script that can be reasonably run from within the PowerShell command prompt, from within the ISE or from within a SQL Server Agent job. The output of this script is entirely textual, for human reading. When run from the command prompt or ISE, the output is colorized using Write-Host.
SQL Server jobs can be set up in two different ways, and both support capturing the output into the SQL Server Agent log viewer:
via a CmdExec step, which is simple command-line execution, where the Job Step command text is an executable and its arguments, so you invoke the powershell.exe executable. Captured output is the stdout/sterr of the process:
powershell.exe -Command x:\pathto\script.ps1 -Arg1 -Arg2 -Etc
via a PowerShell step, where the Job Step command text is raw PS script interpreted by its own embedded PowerShell host implementation. Captured output is whatever is written via Write-Output or Write-Error:
#whatever
Do-WhateverPowershellCommandYouWant
x:\pathto\script.ps1 -Arg1 -Arg2 -Etc
Due to some other foibles of the SQL Server host implementation, I find that you can emit output using either Write-Output or Write-Error, but not both. If the job step fails (i.e. if you throw or Write-Error 'foo' -EA 'Stop'), you only get the error stream in the log and, if it succeeds, you only get the output stream in the log.
Additionally, the embedded PS implementation does not support Write-Host. Up to at least SQL Server 2016, Write-Host throws a System.Management.Automation.Host.HostException with the message A command that prompts the user failed because the host program or the command type does not support user interaction.
To support all of my use-cases, so far, I took to using a custom function Write-Message which was essentially set up like (simplified):
$script:can_write_host = $true
$script:has_errors = $false
$script:message_stream = New-Object Text.StringBuilder
function Write-Message {
Param($message, [Switch]$iserror)
if ($script:can_write_host) {
$private:color = if ($iserror) { 'Red' } else { 'White' }
try { Write-Host $message -ForegroundColor $private:color }
catch [Management.Automation.Host.HostException] { $script:can_write_host = $false }
}
if (-not $script:can_write_host) {
$script:message_stream.AppendLine($message) | Out-Null
}
if ($iserror) { $script:has_errors = $true }
}
try {
<# MAIN SCRIPT BODY RUNS HERE #>
}
catch {
Write-Message -Message ("Unhandled error: " + ($_ | Format-List | Out-String)) -IsError
}
finally {
if (-not $script:can_write_host) {
if ($script:has_errors) { Write-Error ($script:message_stream.ToString()) -EA 'Stop' }
else { Write-Output ($script:message_stream.ToString()) }
}
}
As of SQL Server 2019 (perhaps earlier), it appears Write-Host no longer throws an exception in the embedded SQL Server Agent PS host, but is instead a no-op that emits nothing to either output or error streams. Since there is no exception, my script's Write-Message function can no longer reliably detect whether it should use Write-Host or StringBuilder.AppendLine.
The basic workaround for SQL Server Agent jobs is to use the more-mature CmdExec step type (where Write-Output and Write-Host both get captured as stdout), but I do prefer the PowerShell step type for (among other reasons) its ability to split the command reliably across multiple lines, so I am keen to see if there is a more-holistic, PowerShell-based approach to solve the problem of whether Write-Host does anything useful for the host I am in.
Just check if your host is UserInteractive or an service type environment.
$script:can_write_host = [Environment]::UserInteractive
Another way to track the output of a script in real time is to push that output to a log file and then monitor it in real time using trace32. This is just a workaround, but it might work out for you.
Add-Content -Path "C:\Users\username\Documents\PS_log.log" -Value $variablewithvalue
I am having to run a Microsoft cmdlet, and the important bit of information is written to console using a Write-Host line within the cmdlet.
It is NOT returned, so I cannot do $result = Commandlet ...
A different value is returned that is not of use to me, what I actually need is printed to console within the commandlet is there anyway I can 'sniff' or 'scrape' the console to get the information I want?
$result = Test-Cluser
Test-Cluster will print stuff like: 'HadUnselectedTests', 'ClusterConditionallyApproved', etc.
But the value it returns in the path to the .htm report file.
And the .htm report file does not contain one of those status codes unfortunately so I cannot just parse the .htm file for it either.
Any suggestions?
Note: As for why you should never use Write-Host to output data, see this answer.
In PSv5+:
$result = Test-Cluster 6>&1
Since version 5, Write-Host writes to the newly introduced information stream, whose number is 6.
6>&1 redirects that stream to the success output stream (number 1), so that it too can be captured in $result.
Caveat: The related Out-Host cmdlet does not write to the information stream; its output cannot be captured - see this answer for the differences between Write-Host and Out-Host.
In PSv4-:
There is no way to capture Write-Host output in-session.
The only workaround is to launch another instance of Powershell with the target command specified as a string.
Caveats:
Such an invocation is slow,
prevents passing of arguments with their original data type
invariably only returns string data (lines of text)
returns output from all output streams, including error output
for a list of all output streams, see Get-Help about_Redirection
$result = powershell -noprofile -command 'Test-Cluster'
Note that using a script block to pass the command (-command { Test-Cluster }) would not work, because PowerShell then uses serialization and deserialization to emulate the in-session behavior.
Optional reading: output streams in PowerShell and how to redirect them:
Get-Help about_Redirection discusses a list of all output streams, which can be targeted by their numbers; since PSv5, these are:
1 ... success output stream (implicit output and Write-Output output)
2 ... error output stream (Write-Error and unhandled errors)
3 ... warnings (Write-Warning)
4 ... verbose output (Write-Verbose)
5 ... debug output (Write-Debug)
6 ... (v5+) Write-Information and Write-Host output
Note that some streams are silent by default and require opt-in to produce output, either via a preference variable (e.g., $VerbosePreference) or a common parameter (e.g., -Verbose)
{n}> allows redirecting the number {n} stream; if {n} is omitted, 1 is implied:
to a file (e.g., 3> c:/tmp/warnings.txt
to "nowhere", i.e suppressing the output (e.g., 3> $null)
to the success output stream (e.g., 3>&1); note: only stream 1 can be targeted this way.
*> targets all output streams.
Note: Unlike in POSIX-like shells (e.g., bash), the order of multiple redirection expression does not matter.
Therefore, the following POSIX-like shell idiom - which redirects error output to the success stream and silences only the original success output - does NOT work:
... 2>&1 1>$null # !! NO output in PowerShell
To achieve this in PowerShell, you mustn't redirect 1 and instead filter the objects in the success by their stream of origin.
Case in point: In the end, the OP wanted the following: capture only warning output, without the regular (success) output:
Test-Cluster 3>&1 | Where-Object { $_ -is [System.Management.Automation.WarningRecord] }
Objects that came from the warning stream have type [System.Management.Automation.WarningRecord], which is what enables the filtering above.
I use *> instead of > to redirect all outputs from console to a file.
Example of redirecting text and a variable to a file:
"The "+$set_groups+"ADGroup set!" | Out-File -FilePath $log_path -Append
I tried using net use, net share, among others - none of which return the expected output. So instead, I'm modifying a script I found to see which network drives/shares are mapped to a user the script is pushed to. Then I go to my log file, look at the data, and determine if the account is set up properly. Here's the current script:
Get-WmiObject Win32_MappedLogicalDisk -computer $env:COMPUTERNAME | select name, providername
Out-File -filepath "\\*UNC filepath*\Mapped_Drives_$env:USERNAME$(get-date -Format _MM-dd-yy" # "HH.mm.ss" "tt).txt"
When I run it, the log file returns empty and I'm not sure why. I changed "Out-File -filepath" to "Start-Transcript" which isn't working the way I want it to either (with too much verbose output). It outputs fine in my PowerShell ISE with all the proper shares listed, but doesn't work when I navigate to the logged output. What am I missing?
You must pipe the output into the logfile
$logfile = "\\*UNC filepath*\Mapped_Drives_$env:USERNAME$(get-date -Format _MM-dd-yy" # "HH.mm.ss" "tt).txt"
Get-WmiObject Win32_MappedLogicalDisk | select name, providername | Out-File $logfile
On a more general note I'd use the commands to fix the mapped drives right there and then, instead of just writing them to a logfile for later inspection.