I am attempting to query AD to get the phone number for a user then format it to a standard format (###-####). I'm using a Switch statement because I've seen the number in a handful of different formats. With the way I have my code set up though I am getting an error: "The term Switch is not recognized as a name of a cmdlet, function, script file..."
Here's the code:
$ADInfo = Get-ADUser $User.Email.split("#")[0] -Properties * -Server $DC
$User.'Phone Number' = $ADInfo.telephoneNumber | Switch -regex ($_) {
'^\d{5}$'
{
"{0:38#-####}" -f $_
break
}
'^\d{7}$'
{
"{0:###-####}" -f $_
break
}
default
{
break
}
}
Am I misunderstanding how the pipeline works? I suppose I could just save this info to a temp variable and then enter a Switch statement but this seemed like an effective way to use the pipeline.
Anyhow, any help is appreciated! Thanks!
Am I misunderstanding how the pipeline works?
Yes.
The pipeline can only pipe stuff to commands - and switch is not a command, it's a language keyword.
You can wrap your switch statement in a ForEach-Object block and pipe the input to that:
$User.'Phone Number' = $ADInfo.telephoneNumber | ForEach-Object {
Switch -regex ($_) {
'^\d{5}$'
{
"{0:38#-####}" -f $_
}
'^\d{7}$'
{
"{0:###-####}" -f $_
}
}
}
As Ansgar points out, the break statements here are redundant (and not required), since your test cases are mutually exclusive
As suggested by Mathias R. Jessen, here's an example of how to use Switch without having to iterate through things with a ForEach loop, or piping anything to it.
Switch will iterate an array on its own, so there's no need to nest it in a loop, or even pipe anything in this case. It can be done as such:
$ADInfo = Get-ADUser $User.Email.split("#")[0] -Properties * -Server $DC
$User.'Phone Number' = Switch -regex ($ADInfo.telephoneNumber) {
'^\d{5}$'
{
"{0:38#-####}" -f $_
}
'^\d{7}$'
{
"{0:###-####}" -f $_
}
}
Aside from that I would recommend using Continue instead of Break. Further example of when continue would be used within the Switch scriptblock:
$TestData = #('John.Doe#Company.Com','JDoe','(555)867-5309','Taco Tuesday')
Switch -regex ($TestData){
'#' {"$_ is an email address.";continue}
'^[+()\-\d\s]+$' {"$_ is a phone number.";continue}
default {"$_ is unknown."}
}
Here we have something being done in the default block, so we want to include a continue statement in the previous cases so that if a match is found it moves to the next item in the array, and does not execute the default case. The output of that would be:
John.Doe#Company.com is an email address.
JDoe is unknown.
(555)867-5309 is a phone number.
Taco Tuesday is unknown.
Related
If I need to filter processes by a module's FileName, the following code does the job:
Get-Process | where { $_.Modules.FileName -eq "xxx\yyy.dll”) }
But if I need to filter modules by FileName starting with a string, the following code doesn't seem to work:
Get-Process | where { $_.Modules.FileName.StartsWith("xxx\yyy.dll”)) }
As result, I see all the processes in the output. I'm very confused why filtering doesn't seem to work in case of StartsWith
The member modules might be a collection. Thus it needs to be iterated too. Like so,
(get-process) | % {
if($_.modules -ne $null) { # No modules, no action
$_.modules | ? { $_.filename.tolower().startswith("c:\program") }
}
}
As for the question, there are actually two iterations. Let's use explicit variables instead of pipelining and printing the acutal module files. Passing multiple $_s around is not easy to read syntax anyway. Like so,
foreach ($p in get-process) {
if ($p.modules -ne $null){
write-host $p.id $p.ProcessName
foreach($m in $p.modules){
if ($m.filename.tolower().startswith("c:\program") ) {
write-host `t $m.moduleName $m.FileName # ` markdown bug
}
}
write-host
}
}
Here is the code below I am using:
$ProcessesToCheckFor = (
'company_name_uat-Historian'
)
$FoundProcesses = Get-Process -Name $ProcessesToCheckFor -ErrorAction SilentlyContinue
foreach ($Item in $ProcessesToCheckFor)
{
if ($FoundProcesses.Name -contains $Item)
{
'{0} runn1ng' -f $Item
}
else
{
'{0} fai1ed' -f $Item
}
}
The code checks to see if company_name_uat-Historian process is running on a server, and if it is running, it will output runn1ng and fai1ed if not.
The problem is, it works when checking only one process just like the code above, but not when you try to check a list of processes.
I need to check a list of processes, so when I chain the rest as listed below:
$ProcessesToCheckFor = (
'company_name_uat-Historian',
'company_name_uat-KEReviewCollector',
'company_name_uat-lwm',
'company_name_uat-MQAck',
'company_name_uat-MQOutput',
'company_name_uat-SQAC',
'company_name_uat-Store',
'company_name_uat-Store_STS',
'company_name_uat-StoreLegacy',
'spotify'
)
$FoundProcesses = Get-Process -Name $ProcessesToCheckFor -ErrorAction SilentlyContinue
foreach ($Item in $ProcessesToCheckFor)
{
if ($FoundProcesses.Name -contains $Item)
{
'{0} runn1ng' -f $Item
}
else
{
'{0} fai1ed' -f $Item
}
They all output fai1ed.
If I do them one by one, each different process will return runn1ng, just all bunched up together they return fai1ed.
Side notes (if anyone is wondering):
runn1ng and fai1ed are 'code words' that get replaced by images using JavaScript. I am making an HTML dashboard that monitors my Windows servers for work, so think green checkmarks and red x marks and what not.
Using PowerShell version 2 is not my choice at all, some of the servers I am responsible for monitoring are Windows 2008 R2. I believe they are going to be updated sometime this year, but the project deliverable requires me to enact a monitoring solution immediately. There are also some servers which are 2012 that I love to write PowerShell scripts for.
spotify is listed as a process because I know it's a legit process but is not installed on our servers. When I was writing the script, I initially tested on my own personal machine and used spotify as a means to test runn1ng if I opened it or fai1ed if I closed it. If all my processes are runn1ng and spotify is fai1ed, then that's an indication that the PS code is working.
It appears to happen in PowerShell version 2.
Any ideas what is causing it and how I can rewrite it?
The Answer
if (($FoundProcesses | Select-Object -ExpandProperty Name) -contains $Item) {
Why
PowerShell 3 added something called member enumeration. If you have an array of objects in 2.0, you can't directly call properties of the array, because it looks for those properties on the array object itself. In 3.0+ if the member doesn't exist on the array, it will check the items for the member as well. Using Select-Object -ExpandProperty is a more explicit way of calling the members.
You could also just move the Get-Process call into the foreach loop.
foreach ($Item in $ProcessesToCheckFor)
{
if (Get-Process -Name $Item -ErrorAction SilentlyContinue)
{
'{0} runn1ng' -f $Item
}
else
{
'{0} fai1ed' -f $Item
}
}
Patrick Meinecke's answer is effective and helpful, but let me attempt a more detailed explanation and a more efficient solution:
First things first: Your code works just fine in PSv3+, which should provide an incentive to leave PSv2 behind.
PSv3 introduced unified handling of scalars and collections through member(-access) enumeration, which bridged the great scalar/array divide that plagued PSv2-.
Specifically, in PSv3 you needn't worry about whether Get-Process -Name $ProcessesToCheckFor happens to return a single or multiple items: in either case you can call .Count on the output to count the number of items returned and you can invoke a member (property) on the result, which, in the case of an array result (multiple items) means that the member is called on each element (e.g., .Name), and the result can be again be used as a scalar or array as needed.
In PSv2-, however, you need to be aware of whether a cmdlet - situationally - happens to return a single item or multiple ones, which means:
Use array-subexpression operator #(...) to ensure that even if the enclosed command happens to return a single item (scalar), the result is an array.
To collect the property values of the elements of a given array or of a scalar in a new array or scalar, use ... | Select-Object -ExpandProperty - again, apply #(...) to ensure that the result is an array.
Note that PSv2- had some unified scalar/array handling, but only with respect to pipeline processing: to send a scalar (single item) to the pipeline, you can use it as-is, without needing to wrap it in #(...):
'foo' | ... works just fine - no need for #('foo') | ... (even though that works too).
Applied to your code we get:
$ProcessesToCheckFor = (
'company_name_uat-Historian',
'company_name_uat-KEReviewCollector',
'company_name_uat-lwm',
'company_name_uat-MQAck',
'company_name_uat-MQOutput',
'company_name_uat-SQAC',
'company_name_uat-Store',
'company_name_uat-Store_STS',
'company_name_uat-StoreLegacy',
'spotify'
)
# Collect the names of all running processes and
# make sure the result is an array (`#(...)`).
$FoundProcesses = #(Get-Process -Name $ProcessesToCheckFor -ErrorAction SilentlyContinue |
Select-Object -ExpandProperty Name)
foreach ($Item in $ProcessesToCheckFor) {
if ($FoundProcesses -contains $Item) {
'{0} runn1ng' -f $Item
} else {
'{0} fai1ed' -f $Item
}
}
So here's what I'm attempting to do:
I manually input a name, and then I want to get a list of users who work under the person whose name I input (extensionattribute9 is who the user works under). However, for each person that works under that person, I also want to run the process for them, and see if anyone works under them as well. I want this process to continue until no one works under the current user.
I've managed do to this up to 3 times without a while loop, but as I don't know how deep I would have to go to get everyone, I feel using a while loop would be better overall, especially in terms of code length.
Here is the code I currently have:
$users = Get-ADUser -Filter * -Properties extensionattribute9,Displayname,mail,title
$users | ForEach-Object {
if ($_.extensionattribute9 -like '*Lynn, *')
{
$_ | select Displayname,userprincipalname,title,extensionattribute9
$name = $_.Displayname
while ($_.extensionattribute9 -ne $null){ $users | ForEach-Object {
if ($_.extensionattribute9 -eq $name)
{
$_ | select Displayname,userprincipalname,title,extensionattribute9
$name=$_.Displayname
}
}
}
}
}
When I run the code I get a user (User A) under 'Lynn', and then a user under User A. After that, nothing. The program still continues to run, but nothing gets returned. I'm guessing it's stuck in an infinite cycle, but I don't know where better to put the while loop. Help?
It sounds like you are trying to do a recursive search with nested while/for-each loops which can end badly. You can try something like this:
Function Get-Manager {
param([Object]$User)
$ManagerName = $User.extensionattribute9
# Recursion base case
if ($ManagerName -eq $null){
return
}
# Do what you want with the user here
$User | select Displayname, userprincipalname, title, extensionattribute9
# Recursive call to find manager's manager
$Manager = Get-ADUser -Filter "Name -like $ManagerName"
Get-Manager $Manager
}
# Loop through all ADusers as you did before
$Users = Get-ADUser -Filter * -Properties extensionattribute9,Displayname,mail,title
Foreach ($User in $Users) {
Get-Manager $User
}
Please note I don't have experience using Powershell with Active Directory so the syntax may be incorrect, but shouldn't be hard to fix. Hope this helps!
I'm not familiar with Powershell, but one possible reason you're having trouble is that $_ is being used to mean two different things, depending on whether you use it inside the while loop or not. Is Powershell really smart enough to know what you mean?
More important: the code
$_ | select Displayname,userprincipalname,title,extensionattribute9
$name = $_.Displayname
appears in two places close together. This is a definite code smell. It should appear once and only once.
When you're traversing a hierarchy and you don't know how deep it will go, you must use a recursive algorithm (a function that calls itself). Here it is in pseudocode:
function myFunc ($node) {
//report the name of this node
echo "node $node found." ;
// check to see if this node has any child nodes
array $children = getChildNodes ($node) ;
if (count($children) == 0) {
//no child nodes, get out of here
return ;
}
//repeat the process for each child
foreach($children as $child) {
myFunc($child) ;
}
}
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
}
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" }
}