I have two arrays, $a and $b, within array $a is a string which could partially match one of the entries in $b, assuming I could use a wildcard:
$a = "1", "Computer Name", "2"
$b = "3", "4", "Full Computer Name Here"
foreach ($line in $a) {
foreach ($line2 in $b) {
where "*$line*" -like "*$line2*"
}
}
I've got here after trying all the simple 'this array matches that array', into foreach for one array, then tried all the Select-String to Compare-Object $line $line2 -ExcludeDifferent -IncludeEqual -PassThru, but can't get anything to work.
Ideally, it'd return 'Full Computer Name Here' where it matches.
Have you tried this?
$a = "1","Computer Name","2"
$b = "3","4","Full Computer Name Here"
foreach ($line in $a ) {
$b -match $line
}
EDIT:
Probably not the best answer despite it's simplicity as illustrated by #Ansgar in the comments. Sometimes PowerShell is so inconsistent it makes me wonder why I still use it.
Where-Object doesn't work that way. It reads from a pipeline that you don't have in your code. Also, your comparison is backwards, and you must not add wildcard characters to the reference value.
Change your code to something like this:
foreach ($line in $a) {
$b | Where-Object { $_ -like "*${line}*" }
}
or like this:
foreach ($line in $a) {
foreach ($line2 in $b) {
if ($line2 -like "*${line}*") { $line2 }
}
}
and it will do what you expect.
Edit:
I keep forgetting that comparison operators also work as enumerators, so the latter example could be simplified to something like this (removing the nested loop and conditional):
foreach ($line in $a) {
$b -like "*${line}*"
}
$b | Where {$_ | Select-String $a}
Updated 2018-06-23
Credits for LotsPings' comment to minimize it further to:
Apparently, Select-String has already both iterators in itself and therefore it can be simplified to just:
$b | Select-String $a
PS C:\> $a = "1", "Computer Name", "Other Name"
PS C:\> $b = "Computer", "4", "Full Computer Name Here", "something else", "Also full computer name here"
PS C:\> $b | Select-String $a
Full Computer Name Here
Also full computer name here
Related
I am trying to remove duplicates and leave only unique entries from the output of 2 queries.
I am pulling a list of installed Windows Updates using the following (also stripping 12 chars of whitespace and dropping to lower case:
$A = #(Get-HotFix | select-object #{Expression={$_.HotFixID.ToLower()}} | ft -hidetableheaders | Out-String) -replace '\s{12}',''
I am then querying a list of available files in a folder and stripping 3 trailing whitespace chars using:
$B = #(Get-ChildItem D:\y | select-object 'Name' | ft -hidetableheaders | Out-String) -replace '\s{3}',''
The problem I have is that the first query ($A) returns output like:
kb4040981
kb4041693
kb2345678
kb8765432
While the second query ($B) returns output like:
windows8.1-kb4040981-x64_d1eb05bc8c55c7632779086079c7759f40d7386f.cab
windows8.1-kb4041687-x64_3bdf264bcfc0dda01c2eaf2135e322d2d6ce6c64.cab
windows8.1-kb4041693-x64_359b7ac71a48e5af003d67e3e4b80120a2f5b570.cab
windows8.1-kb4049179-x64_e6ec21d5d16fa6d8ff890c0c6042c2ba38a1f7c4.cab
I need to compare the 2 outputs using wildcards around each entry in the $A array (I think), and where it exists in $B remove the entire line from $B array.
I cannot truncate the output of $B as I need to use the full filenames in a subsequent process.
IE in the example output above, the entire FIRST and THIRD lines would be remove from the $B array and other lines left intact.
I have tried numerous methods from online searches, and used foreach loops, all to no avail.
Thank you in advance for any assistance.
What did you try with foreach loops that didn't work? Unless your output is huge, this method is pretty striaght forward.
$a = "kb4040981","kb4041693","kb2345678","kb8765432","test"
[System.Collections.ArrayList]$b = "windows8.1-kb4040981-x64_d1eb05bc8c55c7632779086079c7759f40d7386f.cab","windows8.1-kb4041687-x64_3bdf264bcfc0dda01c2eaf2135e322d2d6ce6c64.cab","windows8.1-kb4041693-x64_359b7ac71a48e5af003d67e3e4b80120a2f5b570.cab","windows8.1-kb4049179-x64_e6ec21d5d16fa6d8ff890c0c6042c2ba38a1f7c4.cab"
$toRemove = New-Object System.Collections.ArrayList
foreach($kb in $a)
{
foreach($line in $b)
{
if($line -match $kb)
{
write-host "$kb found in: $line" -ForegroundColor Green
$toRemove.add($line) | out-null
}
}
}
foreach($line in $toRemove)
{
$b.Remove($line)
}
$b
Hope it helps.
I would recommend for you to take a little time to learn the very basics of Powershell. When you use format cmdlets and text files instead of objects you cut yourself of the good stuff. ;-)
Here is how I would start the task:
$A = Get-HotFix
$B = Get-ChildItem D:\y | Select-Object -Property Name,#{Name='HotFixID';Expression={($_.BaseName -split '-')[1]}}
Compare-Object -ReferenceObject $A -DifferenceObject $B -Property 'HotFixID' -PassThru
Sincere thanks to sambardo for his patience and input! The final working solution based on his excellent recommendation is:
$a = (Get-Hotfix).hotfixID
$b = (Get-ChildItem D:\y\ -file *.cab).name
$toRemove = New-Object System.Collections.ArrayList
foreach($kb in $a)
{
foreach($line in $b)
{
if($line -match $kb)
{
# write-host "$kb found in: $line" -ForegroundColor Green
$toRemove.add($line) | out-null
}
}
}
foreach($line in $toRemove)
{
$b.Remove($line)
}
$b
I'm having a simple issue with a script, where I want to run a GCI against a remote server, issue is, the value is combined with another hashtable property, so the GCI fails.
The script reads entries from a two-column .csv, the headers are "server" and "platform"
Here's what I've got:
$ShortDate = (Get-Date).ToString('MM/dd/yyyy')
$CheckServer = #{}
$serverObjects = #() # create a list of server objects
Import-Csv $Dir\Servers.csv | ForEach {
$CheckServer.Server = $_.Server
$CheckServer.Platform = $_.Platform
if (GCI \\$_.Server\c$\log\Completed_Summary_*.html -EA 0 | where {$.LastWriteTime -ge "$ShortDate"}) {
Write-Host "FOUND"
} # end of IF GCI
} # end of For-Each
$serverObjects += New-Object -TypeName PSObject -Property $CheckServer
The problem is that the entry for $_.Server should be SERVER1, SERVER2, SERVER3, etc, all the entries in the servers.csv, instead, the values for both $_.Server and $_.Platform are combined. Such as:
Write-Host "Checking" \\#{Server=SERVER1; Platform=PLATFORM_1}.Server\c$\log\Completed_Summary_*.html
it should show as follows:
Write-Host "Checking" \\SERVER1\log\Completed_Summary_*.html
How do I un-combine them so that the GCI command works?
PowerShell only does simple variable expansion inside strings. For more complex expressions like index operations or accessing object properties/methods it would insert the stringified value of the array or object variable and leave the rest of the operation untouched.
Demonstration:
PS C:\> $array = 23, 42
PS C:\> Write-Host "some $array[1] or other"
some 23 42[1] or other
PS C:\> $object = New-Object -Type PSObject -Property #{Foo=23; Bar=42}
PS C:\> Write-Host "some $object.Foo or other"
some #{Bar=42; Foo=23}.Foo or other
To avoid this you need to either:
assign the resulting value to a variable first and use that variable in the string:
$value = $array[5]
Write-Host "some $value or other"
$value = $object.Foo
Write-Host "some $value or other"
use a subexpression ($(...)):
Write-Host "some $($array[5]) or other"
Write-Host "some $($object.Foo) or other"
use the format operator (-f):
Write-Host "some {0} or other" -f $array[5]
Write-Host "some {0} or other" -f $object.Foo
modify like it
$ShortDate = Get-Date -Hour 0 -Minute 0 -Second 0
$CheckServer = #{}
$serverObjects = #() # create a list of server objects
$Dir="C:\temp"
Import-Csv $Dir\Servers.csv | ForEach {
$CheckServer.Server = $_.Server
$CheckServer.Platform = $_.Platform
if (GCI "\\$($_.Server)\c$\log\Completed_Summary_*.html" -EA 0 | where {$_.LastWriteTime -ge $ShortDate})
{
Write-Host "FOUND"
}
}
I have a text file domains.txt
$domains = ‘c:\domains.txt’
$list = Get-Content $domains
google.com
google.js
and an array
$array = #(".php",".zip",".html",".htm",".js",".png",".ico",".0",".jpg")
anything in $domain that ends in something in #arr should NOT be in my final list
So google.com would be in final list but google.js would not.
I found some other stackoverflow code that give me the exact opposite of what I'm looking for but, hah I can't get it reversed!!!!
This gives me the exact opposite of what I want, how do I reverse it?
$domains = ‘c:\domains.txt’
$list = Get-Content $domains
$array = #(".php",".zip",".html",".htm",".js",".png",".ico",".0",".jpg")
$found = #{}
$list | % {
$line = $_
foreach ($item in $array) {
if ($line -match $item) { $found[$line] = $true }
}
}
$found.Keys | write-host
this gives me google.js I need it to give me google.com.
I've tried -notmatch etc and can't get it to reverse.
Thanks in advance and the more explanation the better!
Take the .s off, mash the items together into a regex OR, tag on an end-of-string anchor, and filter the domains against it.
$array = #("php","zip","html","htm","js","png","ico","0","jpg")
# build a regex of
# .(php|zip|html|htm|...)$
# and filter the list with it
$list -notmatch "\.($($array -join '|'))`$"
Anyway, the simple way to invert your result is to walk through $found.keys | where { $_ -notin $list }. Or to change your test to $line -notmatch $item.
But beware that you are doing a regex match and something like top500.org would match .0 and throw your results out. If you need to match at the end specifically, you need to use something like $line.EndsWith($item).
other solution
$array = #(".php",".zip",".html",".htm",".js",".png",".ico",".0",".jpg")
get-content C:\domains.txt | where {[System.IO.Path]::GetExtension($_) -notin $array}
So I have a few arrays with names that I want to search though, I would like to keep the arrays separate as they are each specific to a certain group of names. I'm trying to figure out how to search though more then one at the same time. The code I have below is how to search though one array but I'm not sure the best way to search multiple. I tried to add -and $array2 into the foreach but that did not work.
I know I could just add the same block for each array but I'm wondering if there is a cleaner and more efficient way to do that.
$array1 = "name1", "name2", "name3"
$array2 = "name4", "name5", "name6"
$searchname = Read-Host "Enter the name to search for"
foreach($name in $array1){
if($searchname -eq $name){
Write-Host "$searchname found"
}
}
If you just need to verify whether the name is present in any of the arrays you could simply concatenate them and check if the result contains the name you're looking for:
if (($array1 + $array2) -contains $name) {
Write-Host "$name found"
}
If you want to identify the array in which it was found you could do something like this:
'array1', 'array2' | ForEach-Object {
if ((Get-Variable $_).Value -contains $name) {
Write-Host "$name found in `$$_"
break
}
}
or like this, if the arrays were stored in a hashtable rather than individual variables:
$hash = #{
array1 = "name1", "name2", "name3"
array2 = "name4", "name5", "name6"
}
$hash.GetEnumerator() | ForEach-Object {
if ($_.Value -contains $name) {
Write-Host ('{0} found in ${1}' -f $name, $_.Name)
break
}
}
If you want to search across items in multiple arrays, you can concatenate the arrays in the foreach statement like so:
foreach($name in #($array1;$array2)){
if($searchname -eq $name){
Write-Host "$searchname found"
}
}
A more PowerShell-idiomatic approach would entail using the pipeline with the Where-Object filter cmdlet:
#($array1;$array2) |Where-Object {$_ -eq $searchname}
Use the PS3+ -in operator: $value -in $array
or the PS2+ -contains operator: $array -contains $value
In case of big arrays don't concatenate them as it's slow.
Organize the arrays in an array so you can enumerate them easier.
$arrays = #(
#("name1", "name2", "name3")
#("name4", "name5", "name6")
)
$searchname = Read-Host "Enter the name to search for"
1..$arrays.count | ForEach {
if ($searchname -in $arrays[$_-1]) {
Write-Host "$searchname found in array #$_"
}
}
Or use a hashtable:
$arrays = #{
foo = "name1", "name2", "name3"
bar = "name4", "name5", "name6"
}
$searchname = Read-Host "Enter the name to search for"
ForEach ($entry in $arrays.GetEnumerator()) {
if ($searchname -in $entry.value) {
Write-Host "$searchname found in array $($entry.key)"
}
}
I am parsing a bunch of data in a textfile. I get the data with Get-Content.
Then I loop through each row in $data. Split each row on a space and load those values into an array.
I then loop through each $string in the array.
If the $string matches a specific value I want to delete it out of the array.
$index.Delete(), $index.Remove() does not work, Here is what I have.
$data = Get-Content "C:\Users\$userName\Desktop\test-data.txt"
foreach($row in $data){
if($row)
{
[Array]$index = $row.Split(" ")
$i = 0
foreach($string in $index){
Write-Host $string
if($string -eq "value1" -or $string -eq "value2" -or $string -eq "value3")
{
$index.Delete() //This does not work.
}
}
I have also tried something like this as well but it just was not working out at all.
for($i -eq $index.length; $i -le 0; $i++)
{
Write-Host $index[$i] #this would hit once then give me an error saying the value is null
if($index[$i] -eq "value1" -or $index[$i] -eq "value2" -or $index[$i] -eq "value3")
{
$index.Remove() #does not hit here at all/nor will it work.
Write-Host $index
}
}
How do I remove something from the $index array..?
Is there a better way to do this?
Any help would be much appreciated, thanks.
The easiest way would be to chain -ne operators:
[Array]$index = $row.Split(" ") -ne $value1 -ne $value2 -ne $value3
Each one will remove all the elements of the array that match the value in the variable, and the result will be passed on to the next. When it's finished, the array will contain the elements the didn't match any of the $value variables.
Try this:
[array]$index = $row.Split(" ",[stringSplitOptions]::RemoveEmptyEntries) -notmatch "\b(?:$value1|$value2|$value3)\b"