powershell string comparison error - validation

I'm comparing 2 strings stored in a variable and validating them against a couple of conditions. See Code Below:
do
{
Write-Host -ForegroundColor 'Yellow' "Enter Users Password `nMust be 8 or more characters long and contain a UPPER case character and a Digit."
$Password = Read-Host "First time" -AsSecureString
$Passcheck = Read-Host "And again" -AsSecureString
$PassConvertFirst = [System.Runtime.InteropServices.Marshal]::SecureStringtoBSTR($Password) ; $PlainPass1 = [system.Runtime.InteropServices.Marshal]::PtrToStringAuto($passconvertfirst)
$PassConvertSecond = [System.Runtime.InteropServices.Marshal]::SecureStringtoBSTR($Passcheck) ; $PlainPass2 = [system.Runtime.InteropServices.Marshal]::PtrToStringAuto($Passconvertsecond)
if ($PlainPass1 -Contains $PlainPass2 -and $PlainPass1.Length -ge 8 -or $PlainPass2.Length -ge 8)
{
$PassCheckBreak = $false
$PlainPassword = $PlainPass2
}
else
{
Write-Warning "Passwords don't match, Try again.."
$PassCheckBreak = $true
}
}
while ($PassCheckBreak)
While this works 99.99% of the time i'm coming across a weird error when I try and compare this:
P4ssw0rd with P4ssword
Using the code above this gets validated as true! which makes no sense as if I try validating
W0rd with word
It fails validation.
I've tried changing to a comparative operator and I'm still getting the same issue.
Any thoughts or ideas?
Many Thanks,
Nigel Tatschner

The problem is within the if statement.
Let's have a closer look:
$PlainPass1 -Contains $PlainPass2 -and $PlainPass1.Length -ge 8 -or $PlainPass2.Length -ge 8
Evaluate each comparision:
$PlainPass1 -Contains $PlainPass2 # False
$PlainPass1.Length -ge 8 # True
$PlainPass2.Length -ge 8 #true
Thus the outcome is:
$false -and $true -or $true
Anything -or $true is true. Lesson: mind the binding order. When in doubt, use parenthesis.

Related

Can not compare Values of Variable with strings in Powershell

I am trying to get info about an iSCSI-Targets Connectionstate.
Ideally I want to have a variable with a value that is True or False for connected or not connected, so that i can run commands to connect or disconnect as needed.
If i set the Variable directly it's working fine. Setting the Variable with the output of a cmdlet seems to work but comparing the Values False or True with IF is always resulting in True. For me the question is: where is the difference between setting the Variable directly to True and using the output of the cmdlet...?
Sample one:
$IsConnected=true
if($IsConnected -eq 'true') {
echo IsConnected!
} else {
echo IsNotConnected
}
Results, as expected, in: IsConnected
Sample two:
$IsConnected=False
if($IsConnected -eq 'true') {
echo IsConnected!
} else {
echo IsNotConnected
}
Results, as expected, in: IsNotConnected
So now my knowledge ends...:
Sample three:
PS ~> $IsConnected=get-iscsitarget | select -ExpandProperty IsConnected
PS ~> echo $IsConnected
True
PS ~> if($IsConnected -eq 'True') {Echo "IsConnected"}else{echo "IsNotConnected"}
IsConnected
PS ~> if($IsConnected -eq 'False') {Echo "IsConnected"}else{echo "IsNotConnected"}
IsConnected
PS ~> if($IsConnected -eq '17beerarenotenaugh') {Echo "IsConnected"}else{echo "IsNotConnected"}
IsConnected
PowerShell's comparison operators are overloaded, meaning they might have different behaviors based on the type of operand you provide.
The behavior depends entirely on the type of the left-hand side (lhs) operand you provide, and PowerShell will try to convert the right-hand side (rhs) to the same.
In your example, the value of the $IsConnected variable is a [bool] - and PowerShell therefore attempts to convert the rhs operand to a [bool] to.
The conversion logic for [string] to [bool] is:
Empty strings = $false
Non-empty strings = $true
Since 'True', 'False' and '17beerarenotenaugh' are all non-empty, the if conditions are basically interpreted as:
if($IsConnected -eq $true) { ... }
if($IsConnected -eq $true) { ... }
if($IsConnected -eq $true) { ... }
Use the automatic variables $true and $false to avoid this:
PS ~> if($IsConnected -eq $false) {Echo "IsConnected"}else{echo "IsNotConnected"}
IsNotConnected
alternatively, use the -not operator:
PS ~> if(-not $IsConnected) {Echo "IsConnected"}else{echo "IsNotConnected"}
IsNotConnected
thanks for helping!. Our NAS disconnects sometimes or just don't connect after Reboot so maybe some others find this usefull...:
$IsConnected = Get-IscsiTarget | Select-Object -ExpandProperty IsConnected
If($IsConnected -eq $false)
{
Get-IscsiTarget | Where-Object -Property NodeAddress -Like "iqn.2000-01.com.synology:nas01.Target-myiqnnumber" |
Connect-IscsiTarget -AuthenticationType ONEWAYCHAP -ChapUsername mychapuser -ChapSecret mychapuserssecret
}
else
{
echo "IsConnected=$Isconnected iSCSI-Drive:OK"
}

Powershell Plus or Minus Comparison Operator (Fuzzy Logic)?

So let me tell you what I'm trying to do here. Our SolarWinds alerts report on disk capacity as read by Windows, not the Virtual Machine vDisk size setting. What I'm trying to do is match the size so that I can find the correct vDisk and report on its datastore free space to determine whether or not we can add more.
Here's the problem, the GB number never matches between Windows and VMWare. Say the disk has a 149.67 capacity as reported by Windows, well the VMWare setting is 150, or 150.18854, or anything of that sort. I cannot find the vdisk without knowing the exact number, but theoretically I could find it if I could just say, have a comparison operator that had some breathing room, like plus or minus 1 or even 0.5. So for example:
Get-HardDisk -Vm SERVERNAME | Where-Object {
$_.CapacityGB -lt $size + 0.5 -and
$_.CapacityGB -gt $size - 0.5
}
This doesn't work though, for whatever reason. I need something similar to this. Any ideas?
UPDATE: Turns out to be user error, I was experimenting with the wrong number when testing the command. I thought it was the syntax, it was the number I was using itself.
So because I managed to answer my own question I thought I'd post a script for achieving this here. Note that you will need to have a txt file with a comma separated servername and capacity. You could probably modify this to do many other things with VMWare data gathering if you wanted. In the end you'll need to know which columns are which and import to Excel as comma delimited.
Most the variables are decimal values.
Also note that I have no yet figured out a way to programatically deal with the discovery of multiple matching disks.
$serverlist = Get-Content "./ServerList.txt"
$logfile = "./Stores.txt"
remove-item "./Stores.txt"
Function LogWrite {
Param (
[string]$srv,
[string]$disk,
[string]$store
)
Add-Content $logfile -value $srv","$disk","$store
}
foreach ($item in $serverlist){
$store = "Blank"
$disk = "Blank"
try {
$server,$arg = $item.split(',')
$round = [math]::Round($arg,0)
$disk = get-harddisk -vm $server | where-object{$_.CapacityGB -lt ($round + 2) -and $_.CapacityGB -gt ($round - 2) }
if ([string]::IsNullOrEmpty($disk)){
$disk = "Problem locating disk."
$store = "N/A"
continue
}
if ($disk.count -gt 1) {
$disk = "More than one matching disk."
$store = "N/A"
} else {
$store = get-harddisk -vm $server | where-object{$_.CapacityGB -lt ($round + 2) -and $_.CapacityGB -gt ($round - 2) } | Get-Datastore | %{ "{0},{1},{2}" -f $_.Name,[math]::Round($_.FreeSpaceGB,1),[math]::Round($_.CapacityGB,1) }
}
}
catch {
$disk = "Physical"
$store = "N/A"
}
LogWrite $server $disk $store
}

powershell user input validation not accepting valid input

I have some code which is behaving strangely and I am not sure why. I am attempting to validate the user input is a number and that it is less than 255. Pretty easy.
Problem is that numbers from 26 to 99 are not valid for me in my testing. 1-25 are fine, and 100+ seem fine too.. but for some reason 26-99 keep me in the loop.
DO
{
$ip_addr_first = Read-Host 'enter a number less than 255'
} while ($ip_addr_first -notmatch '\p{Nd}+' -or $ip_addr_first -gt 255)
write-host 'You entered' $ip_addr_first
suggestions welcome on where the problem is, as I'm at a loss here.
Try
do
{
$ip_addr_first = Read-Host 'Enter an integer between 0 and 255'
} while ($ip_addr_first -notmatch '^\p{Nd}+$' -or [int] $ip_addr_first -gt 255)
write-host 'You entered:' $ip_addr_first
Note the [int] cast, which ensures that $ip_addr_first is compared as a number, which makes all the difference here.
Without it, given that $ip_addr_first is a string (the type returned by Read-Host), -gt performs lexical comparison, and 26 is lexically greater than 255, for instance (the same applies to any number (string) starting with 3 or greater).
Also note that I've anchored your regular expression p{Nd}+ with ^ and $ to ensure that the entire input is matched against it (you could make this more permissive by allowing leading and trailing whitespace).
Alternative approach that uses number parsing only:
[int] $ip_addr_first = -1
do
{
$userInput = Read-Host 'Enter an integer between 0 and 255'
} while (-not ([int]::TryParse($userInput, [ref] $ip_addr_first) -and
$ip_addr_first -ge 0 -and $ip_addr_first -le 255))
write-host 'You entered:' $ip_addr_first

PowerShell -Filters Failing on -notlike comparison

I have this script trying to pull inactive users from AD, but if someone doesn't have a Title, they don't get picked up. I tested the logic on an empty title for a single user, which returned as true, but when it runs through the foreach, it doesn't work (output shows only people with a Title are valid). I thought maybe the lastlogontimestamp comparison wasn't working so I added lastlogondate, but seems the Title is still the problem and I have no idea why?
#Inactivity Process
Import-Module ActiveDirectory
# Gets time stamps for all User in the domain that have NOT logged in since after specified date. Exludes Prism and PlanLink providers.
$DaysInactive = 365
$time = (Get-Date).Adddays(-($DaysInactive))
$Out_file = "C:\scripts\adcleanup\Inactivity-365days-or-longer_$((Get-Date).ToString('MM-dd-yyyy')).csv"
$Out_file2 = "C:\scripts\adcleanup\FailInactivity-365days-or-longer_$((Get-Date).ToString('MM-dd-yyyy')).csv"
# Get all AD User with lastLogonTimestamp less than our time and set to enabled. Check All Users for this...
$Users = Get-ADUser -Filter {samaccountname -like "s0*" -and (enabled -eq $true) -and (Title -notlike "*PRISM*") -and (Title -notlike "*PlanLink*") } -Properties samaccountname,name,enabled,Title,LastLogontimestamp, lastlogondate |
select SamAccountname,Name,Title,#{Name="LastLogonTimeStamp"; Expression={[DateTime]::FromFileTime($_.lastLogonTimestamp)}},lastlogondate, enabled
Foreach ($User in $Users) {
If (($user.LastLogontimestamp -le $time) -or ($user.LastLogondate -le $time)) {
$User| export-csv $Out_file -notypeinformation -append
}
Else {
$User | export-csv $Out_file2 -notypeinformation -append
}
}
I figured it out.
If (($user.LastLogontimestamp -le $time) -or ($user.LastLogondate -le $time) -or ($user.title -eq "") -and ($user.title -notlike "*PRISM*") -and ($User.title -notlike "*Planlink*") -and (!([string]::IsNullOrEmpty($user.lastlogondate)))) {

PowerShell equivalent of LINQ Any()?

I would like to find all directories at the top level from the location of the script that are stored in subversion.
In C# it would be something like this
Directory.GetDirectories(".")
.Where(d=>Directories.GetDirectories(d)
.Any(x => x == "_svn" || ".svn"));
I'm having a bit of difficulty finding the equivalent of "Any()" in PowerShell, and I don't want to go through the awkwardness of calling the extension method.
So far I've got this:
Get-ChildItem | ? {$_.PsIsContainer} | Get-ChildItem -force | ? {$_.PsIsContainer -and $_.Name -eq "_svn" -or $_.Name -eq ".svn"
This finds me the svn directories themselves, but not their parent directories - which is what I want. Bonus points if you can tell me why adding
| Select-Object {$_.Directory}
to the end of that command list simply displays a sequence of blank lines.
To answer the immediate question with a PowerShell v3+ solution:
(Get-ChildItem -Force -Directory -Recurse -Depth 2 -Include '_svn', '.svn').Parent.FullName
-Directory limits the matches to directories, -Recurse -Depth 2 recurses up to three levels (children, grandchildren, and great-grandchildren), Include allows specifying multiple (filename-component) filters, and .Parent.FullName returns the full path of the parent dirs. of the matching dirs., using member-access enumeration (implicitly accessing a collection's elements' properties).
As for the bonus question: select-object {$_.Directory} does not work,
because the \[System.IO.DirectoryInfo\] instances returned by Get-ChildItem have no .Directory property, only a .Parent property; Select-Object -ExpandProperty Parent should have been used.
In addition to only returning the property value of interest, -ExpandProperty also enforces the existence of the property. By contrast, Select-Object {$_.Directory} returns a custom object with a property literally named $_.Directory, whose value is $null, given that the input objects have no .Directory property; these $null values print as empty lines in the console.
As for the more general question about a PowerShell equivalent to LINQ's .Any() method, which indicates [with a Boolean result] whether a given enumerable (collection) has any elements at all / any elements satisfying a given condition:
Natively, PowerShell offers no such equivalent, but the behavior can be emulated:
Using the PowerShell v4+ intrinsic.Where() method:
Caveat: This requires collecting the entire input collection in memory first, which can be problematic with large collections and/or long-running input commands.
(...).Where({ $_ ... }, 'First').Count -gt 0
... represents the command of interest, and $_ ... the condition of interest, applied to each input object, where PowerShell's automatic $_ variable refers to the input object at hand; argument 'First' ensures that the method returns once the first match has been found.
For example:
# See if there's at least one value > 1
PS> (1, 2, 3).Where({ $_ -gt 1 }, 'First').Count -gt 0
True
Using the pipeline: Testing whether a command produced at least one output object [matching a condition]:
The advantage of a pipeline-based solution is that it can act on a command's output one by one, as it is being produced, without needing to collect the entire output in memory first.
If you don't mind that all objects are enumerated - even if you only care if there is at least one - use Paolo Tedesco's helpful extension to JaredPar's helpful answer.
The down-side of this approach is that you always have to wait for a (potentially long-running) command to finish producing all output objects, even though - logically - the determination whether there are any output objects can be made as soon as the first object is received.
If you want to exit the pipeline as soon as one [matching] object has been encountered, you have two options:
[Ad-hoc: Easy to understand, but cumbersome to implement]
Enclose the pipeline in a dummy loop and use break to break out of the pipeline and that loop (... represents the command whose output to test, and $_ ... match the condition):
# Exit on first input object.
[bool] $haveAny = do { ... | % { $true; break } } while ($false)
# Exit on first input object that matches a condition.
[bool] $haveAny = do { ... | % { if ($_ ...) { $true ; break } } } while ($false)
[Use a PowerShell v3+ self-contained utility function that is nontrivial to implement]
See the implementation of function Test-Any below.
It can be added to scripts or, for use in interactive sessions, to your $PROFILE file.
PowerShell v3+: Optimized utility function Test-Any
The function is nontrivial, because as of PowerShell (Core) v7.2.x, there is no direct way to exit a pipeline prematurely, so a workaround based on .NET reflection and a private type is currently necessary.
If you agree that there should be such a feature, take part in the conversation in GitHub issue #3821.
#requires -version 3
Function Test-Any {
[CmdletBinding()]
param(
[ScriptBlock] $Filter,
[Parameter(ValueFromPipeline = $true)] $InputObject
)
process {
if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject)) {
$true # Signal that at least 1 [matching] object was found
# Now that we have our result, stop the upstream commands in the
# pipeline so that they don't create more, no-longer-needed input.
(Add-Type -Passthru -TypeDefinition '
using System.Management.Automation;
namespace net.same2u.PowerShell {
public static class CustomPipelineStopper {
public static void Stop(Cmdlet cmdlet) {
throw (System.Exception) System.Activator.CreateInstance(typeof(Cmdlet).Assembly.GetType("System.Management.Automation.StopUpstreamCommandsException"), cmdlet);
}
}
}')::Stop($PSCmdlet)
}
}
end { $false }
}
if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject)) defaults to true if $Filter wasn't specified, and otherwise evaluates the filter (script block) with the object at hand.
The use of ForEach-Object to evaluate the filter script block ensures that $_ binds to the current pipeline object in all scenarios, as demonstrated in PetSerAl's helpful answer here.
The (Add-Type ... statement uses an ad-hoc type created with C# code that uses reflection to throw the same exception that Select-Object -First (PowerShell v3+) uses internally to stop the pipeline, namely [System.Management.Automation.StopUpstreamCommandsException], which as of PowerShell v5 is still a private type.
Background here:
http://powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspx
A big thank-you to PetSerAl for contributing this code in the comments.
Examples:
PS> #() | Test-Any false
PS> Get-EventLog Application | Test-Any # should return *right away* true
PS> 1, 2, 3 | Test-Any { $_ -gt 1 } # see if any object is > 1 true
Background information
JaredPar's helpful answer and Paolo Tedesco's helpful extension fall short in one respect: they don't exit the pipeline once a match has been found, which can be an important optimization.
Sadly, even as of PowerShell v5, there is no direct way to exit a pipeline prematurely.
If you agree that there should be such a feature, take part in the conversation in GitHub issue #3821.
A naïve optimization of JaredPar's answer actually shortens the code:
# IMPORTANT: ONLY EVER USE THIS INSIDE A PURPOSE-BUILT DUMMY LOOP (see below)
function Test-Any() { process { $true; break } end { $false } }
The process block is only entered if there's at least one element in the pipeline.
Small caveat: By design, if there's no pipeline at all, the process block is still entered, with $_ set to $null, so calling Test-Any outside of a pipeline unhelpfully returns $true. To distinguish between between $null | Test-Any and Test-Any, check $MyInvocation.ExpectingInput, which is $true only in a pipeline: Thanks, PetSerAl
function Test-Any() { process { $MyInvocation.ExpectingInput; break } end { $false } }
$true, written to the output stream, signals that at least one object was found.
break then terminates the pipeline and thus prevents superfluous processing of additional objects. HOWEVER, IT ALSO EXITS ANY ENCLOSING LOOP - break is NOT designed to exit a PIPELINEThanks, PetSerAl
.
If there were a command to exit the pipeline, this is where it would go.
Note that return would simply move on to the next input object.
Since the process block unconditionally executes break, the end block is only reached if the process block was never entered, which implies an empty pipeline, so $false is written to the output stream to signal that.
Unfortunately there is no equivalent in PowerShell. I wrote a blog post about this with a suggestion for a general purpose Test-Any function / filter.
function Test-Any() {
begin {
$any = $false
}
process {
$any = $true
}
end {
$any
}
}
Blog post: Is there anything in that pipeline?
A variation on #JaredPar's answer, to incorporate the test in the Test-Any filter:
function Test-Any {
[CmdletBinding()]
param($EvaluateCondition,
[Parameter(ValueFromPipeline = $true)] $ObjectToTest)
begin {
$any = $false
}
process {
if (-not $any -and (& $EvaluateCondition $ObjectToTest)) {
$any = $true
}
}
end {
$any
}
}
Now I can write "any" tests like
> 1..4 | Test-Any { $_ -gt 3 }
True
> 1..4 | Test-Any { $_ -gt 5 }
False
You can use the original LINQ Any:
[Linq.Enumerable]::Any($list)
It's actually quite simple - just select first $true (formatted for clarity):
[bool] ($source `
| foreach { [bool] (<predicate>) } `
| where { $_ } `
| select -first 1)
Alternative way:
($source `
| where { <predicate> } `
| foreach { $true } `
| select -first 1)
My approach now was:
gci -r -force `
| ? { $_.PSIsContainer -and $_.Name -match "^[._]svn$" } `
| select Parent -Unique
The reason why
select-object {$_.Directory}
doesn't return anything useful is that there is no such property on a DirectoryInfo object. At least not in my PowerShell.
To elaborate on your own answer: PowerShell can treat most non-empty collections as $true, so you can simply do:
$svnDirs = gci `
| ? {$_.PsIsContainer} `
| ? {
gci $_.Name -Force `
| ? {$_.PSIsContainer -and ($_.Name -eq "_svn" -or $_.Name -eq ".svn") }
}
I ended up doing it with a count:
$directoryContainsSvn = {
(Get-ChildItem $_.Name -force | ? {$_.PsIsContainer -and $_.Name -eq "_svn" -or $_.Name -eq ".svn"} | Measure-Object).Count -eq 1
}
$svnDirs = Get-ChildItem | ? {$_.PsIsContainer} | ? $directoryContainsSvn
You can tighten this up a bit:
gci -fo | ?{$_.PSIsContainer -and `
(gci $_ -r -fo | ?{$_.PSIsContainer -and $_ -match '[_.]svn$'})}
Note - passing $__.Name to the nested gci is unnecessary. Passing it $_ is sufficent.
I recommend the following solution:
<#
.SYNOPSIS
Tests if any object in an array matches the expression
.EXAMPLE
#( "red", "blue" ) | Where-Any { $_ -eq "blue" } | Write-Host
#>
function Where-Any
{
[CmdletBinding()]
param(
[Parameter(Mandatory = $True)]
$Condition,
[Parameter(Mandatory = $True, ValueFromPipeline = $True)]
$Item
)
begin {
[bool]$isMatch = $False
}
process {
if (& $Condition $Item) {
[bool]$isMatch = $true
}
}
end {
Write-Output $isMatch
}
}
# optional alias
New-Alias any Where-Any
This is the best method that I found so far (does not iterate over all elements if already found a true, and does not break the pipeline):
From LINQ Any() equivalent in PowerShell
It’s possible to use a built-in $input variable that contains the whole pipeline in a scope of function.
So, the desired code could look like the following:
function Test-Any([scriptBlock] $scriptBlock = {$true}, [scriptBlock] $debugOut = $null)
{
if ($debugOut)
{
Write-Host(“{0} | % {{{1}}}” -f $input, $scriptBlock)
}
$_ret = $false;
$_input = ($input -as [Collections.IEnumerator])
if ($_input)
{
while ($_input.MoveNext())
{
$_ = $_input.Current;
Write-Host $_
if ($debugOut)
{
Write-Host(“Tested: [{0}]” -f (&$debugOut))
}
if (&$scriptBlock)
{
if ($debugOut)
{
Write-Host(“Matched: [{0}]” -f (&$debugOut))
}
$_ret = $true
break
}
}
}
$_ret
}
I think that the best answer here is the function proposed by #JaredPar, but if you like one-liners as I do I'd like to propose following Any one-liner:
# Any item is greater than 5
$result = $arr | %{ $match = $false }{ $match = $match -or $_ -gt 5 }{ $match }
%{ $match = $false }{ $match = $match -or YOUR_CONDITION }{ $match } checks that at least one item match condition.
One note - usually the Any operation evaluates the array until it finds the first item matching the condition. But this code evaluates all items.
Just to mention, you can easily adjust it to become All one-liner:
# All items are greater than zero
$result = $arr | %{ $match = $false }{ $match = $match -and $_ -gt 0 }{ $match }
%{ $match = $false }{ $match = $match -and YOUR_CONDITION }{ $match } checks that all items match condition.
Notice, that to check Any you need -or and to check All you need -and.
I took a more linq-style approach.
I know this question is probably super old. I used this to accomplish my needs:
PS> $searchData = "unn"
PS> $StringData = #("unn", "dew", "tri", "peswar", "pymp")
PS> $delegate = [Func[string,bool]]{ param($d); return $d -eq $searchData }
PS> [Linq.Enumerable]::Any([string[]]$StringData, $delegate)
Taken from here:
https://www.red-gate.com/simple-talk/dotnet/net-framework/high-performance-powershell-linq/#post-71022-_Toc482783751

Resources