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?
CodePudding user response:
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 evenGet-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 becauseexit
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.
- Additionally, the error message you get when it does fail can be confusing: