How to "alias" with a bound parameter without breaking pipelining in Powershell? - powershell

I find myself often tacking |select -first 10 onto the end of commands, and I'd like to shorten that to |s10, |s50, and a couple other variants. So I'd like to do the equivalent of set-alias s10 select-object -first 10.
The standard way you "alias" with bound parameters is to write a function and forward #args along with the extra params. But if I write a function that pipes $input through select-object, I lose streaming.
I could write a begin/process/end function, but I don't know if/how I can forward each of those to equivalents in select-object. I could write my own begin/process/end implementation of select-object that just implements the -first behavior, but that's just wrong...
(My fallback is to add a tab-completion to expand s10, but I'd really rather learn how I can implement a proper function.)
How can I implement a function that forwards to select-object with a parameter I want added but doesn't break pipelining?

I found this source https://blogs.technet.microsoft.com/heyscriptingguy/2011/03/01/proxy-functions-spice-up-your-powershell-core-cmdlets/
In this case, a shortcut/alias for Select-String -First 10, it comes down to:
$metadata = New-Object System.Management.Automation.CommandMetaData (Get-Command Select-Object)
[System.Management.Automation.ProxyCommand]::Create($MetaData) | Out-File -FilePath prxyfunctions.psm1
Open the prxyfunctions.psm1 module file and wrap the complete content in the new function called S10
function S10 {
[CmdletBinding(DefaultParameterSetName = 'DefaultParameter', HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=113387', RemotingCapability = 'None')]
param(
<abbreviated...>
.ForwardHelpTargetName Microsoft.PowerShell.Utility\Select-Object
.ForwardHelpCategory Cmdlet
#>
}
Then in the Begin{} section add one statement $PSBoundParameters.Add('First','10') like below.
begin {
try {
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) {
$PSBoundParameters['OutBuffer'] = 1
}
$PSBoundParameters.Add('First','10')
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Select-Object', [System.Management.Automation.CommandTypes]::Cmdlet)
$scriptCmd = {& $wrappedCmd #PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
}
catch {
throw
}
}
That's it. Save the file, import the module, type a nice short command like gci c:\|s10 and get just 10 results.
If you really want to make things error proof, more coding is needed. If S10 -First 2 is used you'll get a nice error thrown.
EDIT in response to #PetSerAl 's useful comments
Some cmdlets further in the pipeline might not be able to handle the proxied function, for instance Sort-Object. Compare the output of these two lines
-join (20..1 | Select -First 10 | Sort)
11121314151617181920
-join (20..1 | S10 | Sort)
<nothing>
-join (20..1 | S10 -Wait | Sort)
11121314151617181920
It is possible to work around that by using the -Wait parameter on the commandline. Or code the Wait parameter in the proxy function $PSBoundParameters.Add('Wait',$true)
When working with large collections this is unfortunate because it disables the Select-Object feature that stops the pipeline after x elements, resulting in more processing and longer waiting.

never use aliases in production scripts is what I would say (and is considered best practice). If its a bit of test code or something quick and dirty that no-one else will ever use, fair enough, but never in production scripts. Aliases can be removed, changed to run other commands and leave you with unintended results as they are user specific.

Related

collection of preference variables in Powershell

I'm looking into importing my entire set of preference variables into a remote scope. Does PowerShell implement collections of variables? Is there a collection of preference variables? If so, would I be able to import the collection with $using: ?
Something like:
Begin {
$scriptblock = {
Try {
$VerbosePreference = $Using:VerbosePreference
$ErrorActionPreference = $Using:ErrorActionPreference
...
}
Catch{ #Ignore these errors }
...
I'd like to import all the preference variables without specifying individually if possible...
PowerShell (as of v5.1):
doesn't implement collections of variables,
and there is no programmatic way to unambiguously and exhaustively identify all preference variables.
For the current list of all preference variables, see Get-Help about_Preference_Variables.
That said, you can use wildcard expression *Preference with Get-Variable to locate at least some of the preference variables - and perhaps they cover your needs:
> (Get-Variable *Preference).Name
ConfirmPreference
DebugPreference
ErrorActionPreference
InformationPreference
ProgressPreference
VerbosePreference
WarningPreference
WhatIfPreference
As stated, the results are neither guaranteed to be complete (e.g., MaximumHistoryCount is not matched), nor do they exclude potential false positives (nothing prevents you from defining variable $FooPreference, for instance).
If you're willing to extract all variable names from about_Preference_Variables help topic - which is not fully robust - see the bottom of this post.
Overall, the best approximation is probably the following command:
> Get-Variable |
Where-Object {
$_.Name -clike '*Preference' -or
($_.Attributes -and $_.Options -notcontains 'ReadOnly')
} | % Name
ConfirmPreference
DebugPreference
ErrorActionPreference
InformationPreference
MaximumAliasCount
MaximumDriveCount
MaximumErrorCount
MaximumFunctionCount
MaximumHistoryCount
MaximumVariableCount
OutputEncoding
ProgressPreference
PSDefaultParameterValues
VerbosePreference
WarningPreference
WhatIfPreference
This relies on the assumption that only preference variables have (validation) attributes, which is typically true, but, again, you're free to define your own variables with validation attributes, which would then mistakenly be included too.
$_.Options -notcontains 'ReadOnly' weeds out read-only variables, because, by definition, they can't be preference variables if they cannot be modified.
As for using these variables in a remote command / a background job:
There is no elegant solution (and $using: only works with literal variable names), but you can try the following:
# Collect pref. variables, to the best of our ability.
$prefVarDefs = Get-Variable | ? { $_.Name -clike '*Preference' -or ($_.Attributes -and $_.Options -notcontains 'ReadOnly') }
# Pass them to the background/remote script block and have them
# assigned there.
Start-Job { $args | % { set-variable $_.Name $_.Value }; ... } -Args $prefVarDefs
Note that Start-Job is used as an example (because it can be run without remoting and in a non-elevated session), but the same rules apply as with Invoke-Command, for instance, and the same technique can be used with the latter.
Another option is to parse the about_Preference_Variables help topic, which is somewhat brittle however, because it:
relies on the topic's specific formatting
relies on the topic to be both complete and accurate.
(Get-Help about_Preference_Variables) -creplace '(?s)\A.*?\r?\n +?Variable +Default Value\r?\n +?-+ +-+\r?\n(.+?)\r?\n\r?\n.*\Z', '$1' -split '\r?\n' | % { (-split $_)[0] }

How to invoke a powershell scriptblock with $_ automatic variable

What works -
Lets say I have a scriptblock which I use with Select-Object cmdlet.
$jobTypeSelector = `
{
if ($_.Type -eq "Foo")
{
"Bar"
}
elseif ($_.Data -match "-Action ([a-zA-Z]+)")
{
$_.Type + " [" + $Matches[1] + "]"
}
else
{
$_.Type
}
}
$projectedData = $AllJobs | Select-Object -Property State, #{Name="Type"; Expression=$jobTypeSelector}
This works fine, and I get the results as expected.
What I am trying to do -
However, at a later point in code, I want to reuse the scriptblock defined as $jobTypeSelector.
For example, I expected below code to take $fooJob (note that it is a single object) passed as parameter below, and be used for $_ automatic variable in the scriptblock and return me the same result, as it returns when executed in context of Select-Object cmdlet.
$fooType = $jobTypeSelector.Invoke($fooJob)
What doesn't work -
It does not work as I expected and I get back empty value.
What I have already tried -
I checked, the properties are all correctly set, and it's not due to the relevant property itself being blank or $null.
I looked on the internet, and it's seemed pretty hard to find any relevant page on the subject, but I eventually found one which was close to explaining the issue in a slightly different context - calling the script blocks in PowerShell. The blog doesn't directly answer my question, and any relevant explanation only leads to a solution which would be very ugly, hard to read and maintain in my opinion.
Question -
So, what is the best way to invoke a scriptblock for a single object, which uses $_ automatic variable as parameter, (instead of param block)
After fiddling around with varoius options, I ended up sloving the problem, in a sort of Hackish way.. But I find it to be the best solution because it's small, easy to read, maintain and understand.
Even though we are talking about single object, use it in the pipeline (which is when PowerShell defines the $_ automatic variable) with ForEach-Object cmdlet
$fooType = $fooJob | ForEach-Object $jobTypeSelector
You can certainly use foreach or ForEach-Object as you mention.
You can also pipe to the ScriptBlock directly, if you change it from a function ScriptBlock to a filter ScriptBlock by setting IsFilter to $true:
$jobTypeSelector.IsFilter = $true
$fooType = $fooJob | $jobTypeSelector
But, what would be even better is if you used a named function instead of an anonymous ScriptBlock, for example:
function Get-JobType
{
Param (
[object] $Job
)
if ($Job.Type -eq "Foo")
{
"Bar"
}
elseif ($Job.Data -match "-Action ([a-zA-Z]+)")
{
$Job.Type + " [" + $Matches[1] + "]"
}
else
{
$Job.Type
}
}
Then you can use it with Select-Object aka select like this:
$projectedData = $AllJobs |
select -Property State, #{Name="Type"; Expression={Get-JobType $_}}
Or with a single job, like this:
$fooType = Get-JobType $fooJob

Powershell assistance

I am currently using the below PS script to check if the currents months MS patches are installed on the system. The script is set to check the $env:COMPUTERNAME.mbsa and the Patch_NA.txt file and send the result to the $env:COMPUTERNAME.csv file.
I now need to modify this script to also pull information from other POS devices in the same location (C:\Users\Cambridge\SecurityScans) and send the results to the $env:COMPUTERNAME.csv file.
The POS devices are listed like this:
172.26.210.1.mbsa
172.26.210.2.mbsa
172.26.210.3.mbsa
and so forth.
The IP range at all our locations (last octet) is 1 - 60. Any ideas on how I can set this up?
Script:
$logname = "C:\temp\PatchVerify\$env:COMPUTERNAME.csv"
[xml]$x=type "C:\Users\Cambridge\SecurityScans\$env:COMPUTERNAME.mbsa"
#This list is created based on a text file that is provided.
$montlyPatches = type "C:\Temp\PatchVerify\Patches_NA.txt"|
foreach{if ($_ -mat"-KB(? <KB>\d+)"){$matches.KB}}
$patchesNotInstalled=$x.SecScan.check | where {$_.id -eq 500} |foreach{`
$_.detail.updatedata|where {$_.isinstalled -eq "false"}}|Select -expandProperty KBID
$patchesInstalled =$x.SecScan.check | where {$_.id -eq 500} |foreach{`
$_.detail.updatedata|where {$_.isinstalled -eq "true"}}|Select -expandProperty KBID
"Store,Patch,Present"> $logname
$store = "$env:COMPUTERNAME"
foreach ($patch in $montlyPatches)
{
$result = "Unknown"
if ( $patchesInstalled -contains $patch)
{
$result = "YES"
}
if ( $patchesNotInstalled -contains $patch)
{
$result = "NO"
}
"$store,KB$($patch),$result" >>$logname
}
You can find lots of information on creating functions on the web, but a simple example would be:
Function Check-Patches{
Param($FileName)
$logname = "C:\temp\PatchVerify\$FileName.csv"
[xml]$x=type "C:\Users\Cambridge\SecurityScans\$FileName.mbsa"
The rest of your existing code goes here...
}
Check-Patches "$env:ComputerName"
For($i=1;$i -le 60;$i++){
Check-Patches "172.26.210.$i"
}
If you need me to break down anything in that let me know and I'll go into further explanation, but from what you already have it looks like you have a decent grasp on PowerShell theory and just needed to know what resources are available.
Edit: I updated my example to better fit your script, having it accept a file name, and then applying that file name to the $logname and $x variables within the function.
The break down...
First we declare that we are creating a Function using the Function keyword. Following that is the name of the function that you will use later to call it, and an opening curly brace to start the scriptblock that makes up the actual function.
Next is the Param line, which in this case is very simple only declaring one variable as input. This could alternatively be done as Function Check-Patches ($FileName){ but when you start getting into more advanced functions that only gets confusing, so my recommendation is to stick with putting the parameters inside the function's scriptblock. This is the first thing you want inside of your function in most cases, excluding any Help that you would write up for the function.
Then we have updated lines for $logname and [xml]$x that use the $FileName that the function gets as input.
After that comes all of your code that parses the patch logs, and outputs to your CSV, and the closing curly brace that ends the scriptblock, and the function.
Then we call it for the ComputerName, and run a For loop. The For loop runs everything between 1 and 60, and for each loop it uses that number as the last octet of the file name to feed into the function and check those files.
A few comments on the rest of your code. $monthlypatches = could be changed to = type | ?{$_ -match "-KB(? <KB>\d+)"}|%{$matches.KB} so that the results are filtered before the ForEach loop, which could cut down on some time.
On the $patchesInstalled and $patchesNotInstalled lines you don't need the backtick at the end of that line. You can naturally have a linebreak after the beginning of the scriptblock for a ForEach loop. Having it there can be hard to see later if the script breaks, and if there is anything after it (including a space) the script can break and throw errors that are hard to track down.
Lastly, you loop through $x twice, and then $monthlyPatches once, and do a lot of individual writes to the log file. I would suggest creating an array, filling it with custom objects that have 3 properties (Store, Patch, and Present), and then outputting that at the end of the function. That changes things a little bit, but then your function outputs an object, which you could pipe to Export-CSV, or maybe later you could want it to do something else, but at least then you'd have it. To do that I'd run $x through a switch to see if things are installed, then I'd flush out the array by setting all of the monthlypatches that aren't already in that array to Unknown. That would go something like:
Function Check-Patches{
Param($FileName)
$logname = "C:\temp\PatchVerify\$FileName.csv"
[xml]$x=type "C:\Users\Cambridge\SecurityScans\$FileName.mbsa"
$PatchStatus = #()
#This list is created based on a text file that is provided.
$monthlyPatches = GC "C:\Temp\PatchVerify\Patches_NA.txt"|?{$_ -match "-KB(? <KB>\d+)"} | %{$matches.KB}
#Create objects for all the patches in the updatelog that were in the monthly list.
Switch($x.SecScan.Check|?{$_.KBID -in $monthlyPatches -and $_.id -eq 500}){
{$_.detail.updatedata.isinstalled -eq "true"}{$PatchStatus+=[PSCustomObject][Ordered]#{Store=$FileName;Patch=$_.KBID;Present="YES"};Continue}
{$_.detail.updatedata.isinstalled -eq "false"}{$PatchStatus+=[PSCustomObject][Ordered]#{Store=$FileName;Patch=$_.KBID;Present="NO"};Continue}
}
#Populate all of the monthly patches that weren't found on the machine as installed or failed
$monthlyPatches | ?{$_ -notin $PatchStatus.Patch} | %{$PatchStatus += [PSCustomObject][Ordered]#{Store=$FileName;Patch=$_;Present="Unknown"}}
#Output results
$PatchStatus
}
#Check patches on current computer
Check-Patches "$env:ComputerName"|Export-Csv "C:\temp\PatchVerify\$env:ComputerName.csv" -NoTypeInformation
#Check patches on POS Devices
For($i=1;$i -le 60;$i++){
Check-Patches "172.26.210.$i"|Export-Csv "C:\temp\PatchVerify\172.26.210.$i.csv" -NoTypeInformation
}

How do I create an alias to Format-Table in Powershell?

Suppose I have a Powershell Format-Table string, something like:
ls | Format-Table Name, #{expression={$_.Length / 1024}; label='KB'}
I'm happy with the output I get from this, but I don't want to type it every time I use it. I'd like to be able to call it with a simple one-word command, something like:
ls | Format-KiloBytes
I gather I should be defining a function for this, since an alias can't specify the parameters. However, if I define something like:
function kilobytes {format-table Name, #{expression={$_.Length / 1024}; label='KB'}}
then it doesn't have any effect:
PS> ls | format-table Name, #{expression={$_.Length / 1024}; label='KB'}
... Produces the formatted output
PS> ls | kilobytes
... Produces output with unchanged formatting, the same as 'ls'
Edit: It appears I was confused. When experimenting, I'd already created an alias kilobytes that was aliased to Format-Table. I'd forgotten this, but this meant that creating a function kilobytes succeeded without any warning, but subsequently calling kilobytes wasn't calling the newly-created function but the existing alias.
First you can try :
function kilobytes {$input | format-table Name, #{expression={$_.Length / 1024}; label='KB'}}
You can find the explanation of $input in about_Functions. When you use a function in a pipeline, the objects piped to the function are assigned to the $input automatic variable.
Here's a working Filter version:
filter kilobytes {$_ | select Name,#{expression={$_.Length / 1024}; label='KB'}}
Or:
filter kilobytes {[PSCustomObject]#{Name=$_.name;KB=$_.length/1024}}
A proxy function provides a good way to bind one or more parameters while still supporting the original command in a natural way. A search on Bing for "powershell proxy function" will give lots of good details.
Below is a proxy function that does exactly what you want. You can see how you still have the Format-Table parameters like -AutoSize, but no -Property parameter as that has been hard coded.
A smarter version of this proxy might actually support adding additional properties to the hard coded ones.
function Format-Kilobytes
{
[CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkID=113303')]
param(
[switch]${AutoSize},
[switch]${HideTableHeaders},
[switch]${Wrap},
[System.Object]${GroupBy},
[string]${View},
[switch]${ShowError},
[switch]${DisplayError},
[switch]${Force},
[ValidateSet('CoreOnly','EnumOnly','Both')]
[string]${Expand},
[Parameter(ValueFromPipeline=$true)]
[psobject]${InputObject})
begin
{
try {
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Format-Table',
[System.Management.Automation.CommandTypes]::Cmdlet)
$properties = "Name",#{expression={$_.Length / 1024}; label='KB'}
$scriptCmd = {& $wrappedCmd #PSBoundParameters -Property $properties }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process { try { $steppablePipeline.Process($_) } catch { throw } }
end { try { $steppablePipeline.End() } catch { throw } }
<#
.ForwardHelpTargetName Format-Table
.ForwardHelpCategory Cmdlet
#>
}

Is it possible to terminate or stop a PowerShell pipeline from within a filter

I have written a simple PowerShell filter that pushes the current object down the pipeline if its date is between the specified begin and end date. The objects coming down the pipeline are always in ascending date order so as soon as the date exceeds the specified end date I know my work is done and I would like to let tell the pipeline that the upstream commands can abandon their work so that the pipeline can finish its work. I am reading some very large log files and I will frequently want to examine just a portion of the log. I am pretty sure this is not possible but I wanted to ask to be sure.
It is possible to break a pipeline with anything that would otherwise break an outside loop or halt script execution altogether (like throwing an exception). The solution then is to wrap the pipeline in a loop that you can break if you need to stop the pipeline. For example, the below code will return the first item from the pipeline and then break the pipeline by breaking the outside do-while loop:
do {
Get-ChildItem|% { $_;break }
} while ($false)
This functionality can be wrapped into a function like this, where the last line accomplishes the same thing as above:
function Breakable-Pipeline([ScriptBlock]$ScriptBlock) {
do {
. $ScriptBlock
} while ($false)
}
Breakable-Pipeline { Get-ChildItem|% { $_;break } }
It is not possible to stop an upstream command from a downstream command.. it will continue to filter out objects that do not match your criteria, but the first command will process everything it was set to process.
The workaround will be to do more filtering on the upstream cmdlet or function/filter. Working with log files makes it a bit more comoplicated, but perhaps using Select-String and a regular expression to filter out the undesired dates might work for you.
Unless you know how many lines you want to take and from where, the whole file will be read to check for the pattern.
You can throw an exception when ending the pipeline.
gc demo.txt -ReadCount 1 | %{$num=0}{$num++; if($num -eq 5){throw "terminated pipeline!"}else{write-host $_}}
or
Look at this post about how to terminate a pipeline: https://web.archive.org/web/20160829015320/http://powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspx
Not sure about your exact needs, but it may be worth your time to look at Log Parser to see if you can't use a query to filter the data before it even hits the pipe.
If you're willing to use non-public members here is a way to stop the pipeline. It mimics what select-object does. invoke-method (alias im) is a function to invoke non-public methods. select-property (alias selp) is a function to select (similar to select-object) non-public properties - however it automatically acts like -ExpandProperty if only one matching property is found. (I wrote select-property and invoke-method at work, so can't share the source code of those).
# Get the system.management.automation assembly
$script:smaa=[appdomain]::currentdomain.getassemblies()|
? location -like "*system.management.automation*"
# Get the StopUpstreamCommandsException class
$script:upcet=$smaa.gettypes()| ? name -like "*StopUpstreamCommandsException *"
function stop-pipeline {
# Create a StopUpstreamCommandsException
$upce = [activator]::CreateInstance($upcet,#($pscmdlet))
$PipelineProcessor=$pscmdlet.CommandRuntime|select-property PipelineProcessor
$commands = $PipelineProcessor|select-property commands
$commandProcessor= $commands[0]
$ci = $commandProcessor|select-property commandinfo
$upce.RequestingCommandProcessor | im set_commandinfo #($ci)
$cr = $commandProcessor|select-property commandruntime
$upce.RequestingCommandProcessor| im set_commandruntime #($cr)
$null = $PipelineProcessor|
invoke-method recordfailure #($upce, $commandProcessor.command)
if ($commands.count -gt 1) {
$doCompletes = #()
1..($commands.count-1) | % {
write-debug "Stop-pipeline: added DoComplete for $($commands[$_])"
$doCompletes += $commands[$_] | invoke-method DoComplete -returnClosure
}
foreach ($DoComplete in $doCompletes) {
$null = & $DoComplete
}
}
throw $upce
}
EDIT: per mklement0's comment:
Here is a link to the Nivot ink blog on a script on the "poke" module which similarly gives access to non-public members.
As far as additional comments, I don't have meaningful ones at this point. This code just mimics what a decompilation of select-object reveals. The original MS comments (if any) are of course not in the decompilation. Frankly I don't know the purpose of the various types the function uses. Getting that level of understanding would likely require a considerable amount of effort.
My suggestion: get Oisin's poke module. Tweak the code to use that module. And then try it out. If you like the way it works, then use it and don't worry how it works (that's what I did).
Note: I haven't studied "poke" in any depth, but my guess is that it doesn't have anything like -returnClosure. However adding that should be easy as this:
if (-not $returnClosure) {
$methodInfo.Invoke($arguments)
} else {
{$methodInfo.Invoke($arguments)}.GetNewClosure()
}
Here's an - imperfect - implementation of a Stop-Pipeline cmdlet (requires PS v3+), gratefully adapted from this answer:
#requires -version 3
Filter Stop-Pipeline {
$sp = { Select-Object -First 1 }.GetSteppablePipeline($MyInvocation.CommandOrigin)
$sp.Begin($true)
$sp.Process(0)
}
# Example
1..5 | % { if ($_ -gt 2) { Stop-Pipeline }; $_ } # -> 1, 2
Caveat: I don't fully understand how it works, though fundamentally it takes advantage of Select -First's ability to stop the pipeline prematurely (PS v3+). However, in this case there is one crucial difference to how Select -First terminates the pipeline: downstream cmdlets (commands later in the pipeline) do not get a chance to run their end blocks.
Therefore, aggregating cmdlets (those that must receive all input before producing output, such as Sort-Object, Group-Object, and Measure-Object) will not produce output if placed later in the same pipeline; e.g.:
# !! NO output, because Sort-Object never finishes.
1..5 | % { if ($_ -gt 2) { Stop-Pipeline }; $_ } | Sort-Object
Background info that may lead to a better solution:
Thanks to PetSerAl, my answer here shows how to produce the same exception that Select-Object -First uses internally to stop upstream cmdlets.
However, there the exception is thrown from inside the cmdlet that is itself connected to the pipeline to stop, which is not the case here:
Stop-Pipeline, as used in the examples above, is not connected to the pipeline that should be stopped (only the enclosing ForEach-Object (%) block is), so the question is: How can the exception be thrown in the context of the target pipeline?
Try these filters, they'll force the pipeline to stop after the first object -or the first n elements- and store it -them- in a variable; you need to pass the name of the variable, if you don't the object(s) are pushed out but cannot be assigned to a variable.
filter FirstObject ([string]$vName = '') {
if ($vName) {sv $vName $_ -s 1} else {$_}
break
}
filter FirstElements ([int]$max = 2, [string]$vName = '') {
if ($max -le 0) {break} else {$_arr += ,$_}
if (!--$max) {
if ($vName) {sv $vName $_arr -s 1} else {$_arr}
break
}
}
# can't assign to a variable directly
$myLog = get-eventLog security | ... | firstObject
# pass the the varName
get-eventLog security | ... | firstObject myLog
$myLog
# can't assign to a variable directly
$myLogs = get-eventLog security | ... | firstElements 3
# pass the number of elements and the varName
get-eventLog security | ... | firstElements 3 myLogs
$myLogs
####################################
get-eventLog security | % {
if ($_.timegenerated -lt (date 11.09.08) -and`
$_.timegenerated -gt (date 11.01.08)) {$log1 = $_; break}
}
#
$log1
Another option would be to use the -file parameter on a switch statement. Using -file will read the file one line at a time, and you can use break to exit immediately without reading the rest of the file.
switch -file $someFile {
# Parse current line for later matches.
{ $script:line = [DateTime]$_ } { }
# If less than min date, keep looking.
{ $line -lt $minDate } { Write-Host "skipping: $line"; continue }
# If greater than max date, stop checking.
{ $line -gt $maxDate } { Write-Host "stopping: $line"; break }
# Otherwise, date is between min and max.
default { Write-Host "match: $line" }
}