I'm trying to avoid using nested ForEach Loop as part of a larger code. To do this, I'm using the -notcontains operator. Basically, I want to see if a substring exists within a string within an array. If it exists, do nothing, if it does not exist, print "Not Found".
Here is the code...
$arr = #('"value11","value21","value31"','"value12","value22","value32"','"value13","value23","value33"')
if ($arr -notcontains "*`"value24`"*")
{
Write-Host "Not Found"
}
if ($arr -notcontains "*`"value22`"*")
{
Write-Host "Not Found 2"
}
We can see that value24 is not within any strings of the array. However, value22 is within the 2nd string in the array.
Therefor the results should output the following...
Not Found
However, instead I see the following output...
Not Found
Not Found 2
Can anyone tell me why this is happening?
-contains and -notcontains don't operate against patterns.
Luckily, -match and -like and their negative counterparts, when used with an array on the left side, return an array of the items that satisfy the condition:
'apple','ape','vape' -like '*ape'
Returns:
ape
vape
In an if statement, this still works (a 0 count result will be interpreted as $false):
$arr = #('"value11","value21","value31"','"value12","value22","value32"','"value13","value23","value33"')
if ($arr -notlike "*`"value24`"*")
{
Write-Host "Not Found"
}
My take on a solution:
($arr | foreach {$_.contains('"value24"')}) -contains $true
Using the V3 .foreach() method:
($arr.ForEach({$_.contains('"value24"')}).contains($true))
And yet another possibility:
[bool]($arr.where({$_.contains('"value24"')}))
Edit for clearer answer on what I'm looking for...
This is the only way I'm able to figure this out so far. I hope there is a much cleaner solution...
$arr = #('"value11","value21","value31"','"value12","value22","value32"','"value13","value23","value33"')
$itemNotFound = $true
ForEach ($item in $arr)
{
If ($itemNotFound)
{
If ($item -like "*`"value24`"*")
{
$itemNotFound = $false
}
}
}
if ($itemNotFound)
{
Write-Host "Not Found value24"
}
$itemNotFound = $true
ForEach ($item in $arr)
{
If ($itemNotFound)
{
If ($item -like "*`"value22`"*")
{
$itemNotFound = $false
}
}
}
if ($itemNotFound)
{
Write-Host "Not Found value22"
}
output will be:
Not Found value24
Related
I'm working on a script backing up the schema for a database to a version control system. I just realized that replicated objects are being scripted out as well; these objects start with sp_MSupd_, sp_MSdel_, sp_MSins_, and syncobj_0x. I would like to skip these items.
I know I can do something like this:
$ExcludeObjectNames = #("sp_MSupd_", "sp_MSdel_","sp_MSins_","syncobj_0x")
If ($ExcludeObjectNames -contains "sp_MSdel_SampleObject")
{
Write-Host "replicated item found"
}
But this only works for exact matches. I tried adding * to the elements in the array but that did not work. And I know I can use -like, but that seems to only be for single values, not arrays.
I know I can do this
$ObjectNames = "sp_MSdel_SampleObject"
If ($ObjectNames -like "sp_MSupd_*" -OR $ObjectNames -like "sp_MSdel_*" -OR $ObjectNames -like "sp_MSins_*" -OR $ObjectNames -like "syncobj_0x*")
{
Write-Host "replicated item found"
}
This is well and fine, BUT if I find more and more things to omit, this becomes more and more ugly.
Is there a way to use wildcards with -contains on an array?
$ExcludeObjectNames = #("sp_MSupd_", "sp_MSdel_","sp_MSins_","syncobj_0x")
Foreach ($ExcludeObjectName in $ExcludeObjectNames) {
If ("sp_MSdel_SampleObject" -match $ExcludeObjectName)
{
Write-Host "replicated item found"
}
}
Or if you only want to edit variables in the script, you can do:
$ExcludeObjectNames = #("sp_MSupd_", "sp_MSdel_","sp_MSins_","syncobj_0x")
$ObjectToCheck = "sp_MSdel_SampleObject" # This variable contains your original contains target
Foreach ($ExcludeObjectName in $ExcludeObjectNames) {
If ($ObjectToCheck -match $ExcludeObjectName)
{
Write-Host "replicated item found"
}
}
If you don't like looping, you can just build a regex filter from your original array:
$ExcludeObjectNames = #("sp_MSupd_", "sp_MSdel_","sp_MSins_","syncobj_0x")
$ObjectToCheck = "sp_MSdel_SampleObject" # This variable contains your original contains target
$excludeFilter = '(^' + ($excludeobjectnames -join "|^") + ')' # Regex filter that you never have to manually update
If ($ObjectToCheck -match $ExcludeFilter)
{
Write-Host "replicated item found"
}
I'm having issue with my foreach method. I am checking in the registry whether a good amount of programs are installed. How would I write it to say something is not installed one time versus it saying something's not installed for each key it checks? Now, If I place a ElseIf it executes "PowerBroker not installed." about 16 times. This is due to it checking every key and writing it out for each key it does not find a match to the displayname. How do I go about it checking the key and only writing it out one time if it's not installed?? Thanks!
$UninstallKeys = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
foreach($Key in $UninstallKeys){
if($Key.GetValue("DisplayName") -Match "BeyondTrust"){
$PBW = $Key.GetValue("DisplayName")
$PBWV = $Key.GetValue("DisplayVersion")
if ($PBW) {
$PBW = $PBW, $PBWV
}
else {
$PBW = "PowerBroker not installed."
$installsmissing = "True"
}
}
Give this script a whirl. If I've understood the requirement correctly it should give you what you need.
$displayName = "BeyondTrust"
$uninstallKeys = Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
# Filter the keys down by their display name property
$specificUninstallKeys = $uninstallKeys |
Where-Object {
$_.GetValue("DisplayName") -eq $displayName
}
# Did we find any keys of that name?
if ($specificUninstallKeys) {
Write-Output "Keys found: $($specificUninstallKeys.Length)"
}
else {
Write-Output "Sorry pal, no keys by that name here!"
}
# There may be more than one; hence the loop-y requirement here.
foreach ($specificUninstallKey in $specificUninstallKey) {
Write-Output $displayName
Write-Output $specificUninstallKey.GetValue("DisplayVersion")
}
How can I script "Does String -contains -not _" / "Does the string contain anything other than _"?
I'm not stuck as I've found a good enough work around. More curiosity than anything else.
Example:
$String = 1,1,1,2,5
$String -contains !(1)
This always comes up False
My solution at the moments is to remove the 1's and see if it's null like so:
$String2 = $String -ne 1
if ([String]::IsNullOrEmpty($String2)) {
Write-Host "True"
} else {
Write-Host "False"
}
Real World Example:
My script is designed to try a certain action until it works. In this case get-msoluser.
At the end of my script I want to count any errors (and list them later) but there will always be an error listed for "get-msoluser" as it fails until it works. So I'm trying to not include that certain error in the count.
$Errors = $Error.InvocationInfo.MyCommand.Name
if ($Errors -contains !("get-msoluser")) {
Write-Host "There was an error I actually care about"
}
INSTEAD I have to do this:
$Errors = $Error.InvocationInfo.MyCommand.Name
$ErrorsICareAbout = $Errors -ne "get-msoluser"
if ([String]::IsNullOrEmpty($ErrorsICareAbout)) {
Write-Host "$ErrorsICareAbout.Count"
} else {
Write-Host "There were errors you actually cared about"
}
Am I missing something that's right under my nose?
You simply need to use -notcontains or add the not operator around then entire -contains comparison like this:
If ($Errors -notcontains ("get-msoluser"))
or
If (!($Errors -contains ("get-msoluser")))
Rather than filtering out the error, try not producing an error in the first place. To suppress errors from a particular command, you can set the error action to SilentlyContinue.
Write-Error 'fail' -ErrorAction SilentlyContinue
So in the case of retrying until Get-MsOlUser works, you could use something like
while($msolUser -eq $null) {
$msolUser = Get-MsOlUser ... -ErrorAction SilentlyContinue
#Wait a second before retrying.
Start-Sleep -Seconds 1
}
#Now work with $msolUser
(You probably also want to put an upper limit on the number of retries)
I have an array with folder names. I want to loop thru this array and find out if an entry is only 7 in length and only contain numbers.
Can anyone please push me in the correct direction? Thanks!
One solution is while looping through the array names check if the two conditions are met like so:
foreach ($name in "test", "1234567", "test02", "001") {
if ($name.Length -eq 7 -and $name -match '^\d+$'){
Write-Host $name
}
}
As far as i understood your question :
function Is-Numeric ($Value)
{
return $Value -match "^[\d\.]+$"
}
$birds = "owl","crow","robin","wren","jay","123"
foreach ($bird in $birds) { if($bird.length -eq 3 -and (Is-Numeric $bird)) {"$bird"} }
just switch it to your case :)
I have a script snippet that basically gets some unformated xml type output from a command.
Then in a defined filter section I'm transforming that into xml and run a search loop on each node, as from the part below.
What I'm trying to figure out is how I can make a multiple if -and loop, like
if (($CimProperty.VALUE -eq $somevariable) -and ($CimProperty.VALUE -eq $something-else))
The only problem is that since it's a foreach loop it won't take it, as it takes each property at a time and then the 'if statement -and portion' for it, which doesn't work since it's the same xml type property section.
In other words the loop doesn't go through the entire array to identify both conditions from the if statement.
PS code snippet:
filter Import-CimXml
{
$CimXml = [Xml]$_
$CimObj = New-Object -TypeName System.Object
foreach ($CimProperty in $CimXml.SelectNodes(“/INSTANCE/PROPERTY”))
{
if ($CimProperty.VALUE -eq $somevariable)
{
write-host "found it"
}
}
}
I hope the scenario is clear, thanks everyone in advance!
For just two conditions, you can make it fairly straight forward like (the untested);
filter Import-CimXml
{
$foundfirst = $false
$foundsecond = $false
$CimXml = [Xml]$_
$CimObj = New-Object -TypeName System.Object
foreach ($CimProperty in $CimXml.SelectNodes(“/INSTANCE/PROPERTY”))
{
if ($CimProperty.VALUE -eq $somevariable)
{
$foundfirst = $true
}
if ($CimProperty.VALUE -eq $someothervariable)
{
$foundsecond = $true
}
}
if ($foundfirst -and $foundsecond)
{
write-host "found it"
}
}
For more conditions, you may want to use arrays of corresponding matchwords/booleans instead.
Just extend your xpath query to do it all. It's less code and should be more efficient.
filter Import-CimXml
{
$CimXml = [Xml]$_
$CimObj = New-Object -TypeName System.Object
if($CimXml.SelectNodes(“/INSTANCE[PROPERTY='$somevariable' and PROPERTY='$someothervariable']”).Count -gt 0) {
write-host "found it"
}
}