Powershell multiple If-Statements with loop - arrays

I have to check for multiple array entries in an if statement.
if (($Right.IdentityReference -eq $User) -or ($Right.IdentityReference -eq ("Domain\" + $GroupArrayList[0])) -or ($Right.IdentityReference -eq ("Domain\" + $GroupArrayList[1])))
This will continue with $GroupArrayList[2], $GroupArrayList[3], ...
Is there any way how I can go trough every entry of the array? I can't write every position down because the array size is dynamic. How can I create such a loop?

You can use a Foreach
Foreach ($ArrayItem in $GroupArrayList) {
if (($Right.IdentityReference -eq $User) -or ($Right.IdentityReference -eq ("Domain\" + $ArrayItem))) {
# Do stuff
}
}
The variable $ArrayItem will refer to your $GroupArrayList[2], $GroupArrayList[3],...

I don't think you even need a loop for that, but instead use the -contains operator like this:
if (($Right.IdentityReference -eq $User) -or ($GroupArrayList -contains ($Right.IdentityReference -replace '^Domain\\',''))
You simply strip off the Domain\ from the $Right.IdentityReference and see if the string that remains can be found in the $GroupArrayList array.

As you're ORing the comparison's why not testing if -in array?
if ($Right.IdentityReference -in
$User,
("Domain\" + $GroupArrayList[0]),
("Domain\" + $GroupArrayList[1]) ) {

Related

Matching entries in arrays powershell

I have two data sets one is a data table pulled back from a SQL date base and the other is a data set pulled back from Active directory
$HRusers = Invoke-SQL ## this calls a function to get data from SQL
$adusers = get-aduser -filter * -Properties surname,EmployeeID,DisplayName
I want to then match the two together so my code at the moment is
$HRusers | ForEach-Object {
foreach ($user in $aduser){
if ((($_.Surname -eq $user.surname) -And ($_.'First Name' -eq $user.Givenname)) -or (($_.Surname -eq $user.surname) -And ($_.'Known As' -eq $user.Givenname)))
{ do some stuff}
}
}
This does work but as the list for each is several 1000 long it is a lot of looping.
is there any way I can do a search rather than a loop. so the logic is more like
$HRusers | ForEach-Object {
find match in AD list where name matchs and then do some stuff.
}
EDIT
So I tried this
if (($adusers.surname -eq $_.surname) -And ($adusers.Givenname -eq $_.'First Name'))
{
write-host "Found" $adusers.surname
}
Which does give a true / false answer, but i need to extract the $aduser record that it giving the match.
Looking on technet
https://technet.microsoft.com/en-us/library/ee692798.aspx
"note that, with –like, you get back the actual values rather than a Boolean True or False"
So I expected
$users = ($adusers.Givenname -like ($_.'First Name') -and $adusers.surname -like ($_.'surname'))
write-host $users
to return the value but it still only returns true / false?
You can search the array with where-object:
foreach ($hruser in $hrusers) {
$adusers | where-object {$hruser.'First Name' -eq $_.givenname} | ForEach-Object {
'do some stuff'
}
}
Or use the fact that Invoke-SqlCmd returns DataTable which has Select method:
foreach ($aduser in $adusers){
$SelectedHrUsers = $hrusers.Select("surname='$($aduser.surname)'")
}
The former won't be much faster that nested arrays, but DataTable.Select might have good performance.

Powershell: How to find an item in an array thats not an exact match?

I have an array of items like so:
$arr = "server1", "server2", server3"
I have a search value:
$ser = "server3-test"
I want to find similar values in the array without doing modifications to the actual value I'm searching for itself (aka string splits etc).
So something like:
if($arr -contains (similar $ser)) {do stuff}
You can do this by filtering:
if (($arr | Where-Object { $_ -like 'server3*' })) {
# do stuff
}
if ($arr.Where( { $_ -like 'server3*' } )) {
# do stuff
}
You can also take advantage of the fact that -like and -match operators can operate on collections, and return a collection that satisfies the condition, and then further on the fact that an empty collection will evaluate $false:
if ($arr -like 'server3*') {
# do stuff
}
After re-reading your question, I see that this is not quite what you are looking for.
In your example, the value in $arr is a subset of $ser, so you could do this:
if ($arr.Where( { $ser -match $_ } )) { }
But it's not clear if that will always be the case.
Perhaps something more safe, but also more verbose would be to check both against each other:
if (
$arr | Where-Object {
$_ -like "*$ser*" -or
$ser -like "*$_*"
} ) {
# do stuff
}
To know what was actually matched, I'd suggest a slightly different approach:
$arr | ForEach-Object {
if ($_ -like $ser) {
# do stuff
# $_ will refer to the array member that was matched
}
}
The caveat here is that # do stuff will be executed once for every match, instead of once if any match was found.
Another possibility:
$itemsMatched = $arr -like $ser
if ($itemsMatched) {
# do stuff (executed once)
}
In this case $itemsMatched will contain the array members that matched (it may be more than one).

Delete elements in an array if they match a value PowerShell

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"

PowerShell: Set-Content having issues with "file already in use"

I'm working on a PowerShell script that finds all the files with PATTERN within a given DIRECTORY, prints out the relevant lines of the document with the PATTERN highlighted, and then replaces the PATTERN with a provided REPLACE word, then saves the file back. So it actually edits the file.
Except I can't get it to alter the file, because Windows complains about the file already being open. I tried several methods to solve this, but keep running into the issue. Perhaps someone can help:
param(
[string] $pattern = ""
,[string] $replace = ""
,[string] $directory ="."
,[switch] $recurse = $false
,[switch] $caseSensitive = $false)
if($pattern -eq $null -or $pattern -eq "")
{
Write-Error "Please provide a search pattern." ; return
}
if($directory -eq $null -or $directory -eq "")
{
Write-Error "Please provide a directory." ; return
}
if($replace -eq $null -or $replace -eq "")
{
Write-Error "Please provide a string to replace." ; return
}
$regexPattern = $pattern
if($caseSensitive -eq $false) { $regexPattern = "(?i)$regexPattern" }
$regex = New-Object System.Text.RegularExpressions.Regex $regexPattern
function Write-HostAndHighlightPattern([string] $inputText)
{
$index = 0
$length = $inputText.Length
while($index -lt $length)
{
$match = $regex.Match($inputText, $index)
if($match.Success -and $match.Length -gt 0)
{
Write-Host $inputText.SubString($index, $match.Index) -nonewline
Write-Host $match.Value.ToString() -ForegroundColor Red -nonewline
$index = $match.Index + $match.Length
}
else
{
Write-Host $inputText.SubString($index) -nonewline
$index = $inputText.Length
}
}
}
Get-ChildItem $directory -recurse:$recurse |
Select-String -caseSensitive:$caseSensitive -pattern:$pattern |
foreach {
$file = ($directory + $_.FileName)
Write-Host "$($_.FileName)($($_.LineNumber)): " -nonewline
Write-HostAndHighlightPattern $_.Line
%{ Set-Content $file ((Get-Content $file) -replace ([Regex]::Escape("[$pattern]")),"[$replace]")}
Write-Host "`n"
Write-Host "Processed: $($file)"
}
The issue is located within the final block of code, right at the Get-ChildItem call. Of course, some of the code in that block is now a bit mangled due to me trying to fix the problem then stopping, but keep in mind the intent of that part of the script. I want to get the content, replace the words, then save the altered text back to the file I got it from.
Any help at all would be greatly appreciated.
Removed my previous answer, replacing it with this:
Get-ChildItem $directory -recurse:$recurse
foreach {
$file = ($directory + $_.FileName)
(Get-Content $file) | Foreach-object {
$_ -replace ([Regex]::Escape("[$pattern]")),"[$replace]")
} | Set-Content $file
}
Note:
The parentheses around Get-Content to ensure the file is slurped in one go (and therefore closed).
The piping to subsequent commands rather than inlining.
Some of your commands have been removed to ensure it's a simple test.
Just a suggestion but you might try looking at the documentation for the parameters code block. There is a more efficient way to ensure that a parameter is entered if you require it and to throw an error message if the user doesn't.
About_throw: http://technet.microsoft.com/en-us/library/dd819510.aspx
About_functions_advanced_parameters: http://technet.microsoft.com/en-us/library/dd347600.aspx
And then about using Write-Host all the time: http://powershell.com/cs/blogs/donjones/archive/2012/04/06/2012-scripting-games-commentary-stop-using-write-host.aspx
Alright, I finally sat down and just typed everything sequentially in PowerShell, then used that to make my script.
It was actually really simple;
$items = Get-ChildItem $directory -recurse:$recurse
$items |
foreach {
$file = $_.FullName
$content = get-content $file
$newContent = $content -replace $pattern, $replace
Set-Content $file $newcontent
}
Thanks for all your help guys.

How to remove item from an array in PowerShell?

I'm using Powershell 1.0 to remove an item from an Array. Here's my script:
param (
[string]$backupDir = $(throw "Please supply the directory to housekeep"),
[int]$maxAge = 30,
[switch]$NoRecurse,
[switch]$KeepDirectories
)
$days = $maxAge * -1
# do not delete directories with these values in the path
$exclusionList = Get-Content HousekeepBackupsExclusions.txt
if ($NoRecurse)
{
$filesToDelete = Get-ChildItem $backupDir | where-object {$_.PsIsContainer -ne $true -and $_.LastWriteTime -lt $(Get-Date).AddDays($days)}
}
else
{
$filesToDelete = Get-ChildItem $backupDir -Recurse | where-object {$_.PsIsContainer -ne $true -and $_.LastWriteTime -lt $(Get-Date).AddDays($days)}
}
foreach ($file in $filesToDelete)
{
# remove the file from the deleted list if it's an exclusion
foreach ($exclusion in $exclusionList)
{
"Testing to see if $exclusion is in " + $file.FullName
if ($file.FullName.Contains($exclusion)) {$filesToDelete.Remove($file); "FOUND ONE!"}
}
}
I realize that Get-ChildItem in powershell returns a System.Array type. I therefore get this error when trying to use the Remove method:
Method invocation failed because [System.Object[]] doesn't contain a method named 'Remove'.
What I'd like to do is convert $filesToDelete to an ArrayList and then remove items using ArrayList.Remove. Is this a good idea or should I directly manipulate $filesToDelete as a System.Array in some way?
Thanks
The best way to do this is to use Where-Object to perform the filtering and use the returned array.
You can also use #splat to pass multiple parameters to a command (new in V2). If you cannot upgrade (and you should if at all possible, then just collect the output from Get-ChildItems (only repeating that one CmdLet) and do all the filtering in common code).
The working part of your script becomes:
$moreArgs = #{}
if (-not $NoRecurse) {
$moreArgs["Recurse"] = $true
}
$filesToDelete = Get-ChildItem $BackupDir #moreArgs |
where-object {-not $_.PsIsContainer -and
$_.LastWriteTime -lt $(Get-Date).AddDays($days) -and
-not $_.FullName.Contains($exclusion)}
In PSH arrays are immutable, you cannot modify them, but it very easy to create a new one (operators like += on arrays actually create a new array and return that).
I agree with Richard, that Where-Object should be used here. However, it's harder to read.
What I would propose:
# get $filesToDelete and #exclusionList. In V2 use splatting as proposed by Richard.
$res = $filesToDelete | % {
$file = $_
$isExcluded = ($exclusionList | % { $file.FullName.Contains($_) } )
if (!$isExcluded) {
$file
}
}
#the files are in $res
Also note that generally it is not possible to iterate over a collection and change it. You would get an exception.
$a = New-Object System.Collections.ArrayList
$a.AddRange((1,2,3))
foreach($item in $a) { $a.Add($item*$item) }
An error occurred while enumerating through a collection:
At line:1 char:8
+ foreach <<<< ($item in $a) { $a.Add($item*$item) }
+ CategoryInfo : InvalidOperation: (System.Collecti...numeratorSimple:ArrayListEnumeratorSimple) [], RuntimeException
+ FullyQualifiedErrorId : BadEnumeration
This is ancient. But, I wrote these a while ago to add and remove from powershell lists using recursion. It leverages the ability of powershell to do multiple assignment . That is, you can do $a,$b,$c=#('a','b','c') to assign a b and c to their variables. Doing $a,$b=#('a','b','c') assigns 'a' to $a and #('b','c') to $b.
First is by item value. It'll remove the first occurrence.
function Remove-ItemFromList ($Item,[array]$List(throw"the item $item was not in the list"),[array]$chckd_list=#())
{
if ($list.length -lt 1 ) { throw "the item $item was not in the list" }
$check_item,$temp_list=$list
if ($check_item -eq $item )
{
$chckd_list+=$temp_list
return $chckd_list
}
else
{
$chckd_list+=$check_item
return (Remove-ItemFromList -item $item -chckd_list $chckd_list -list $temp_list )
}
}
This one removes by index. You can probably mess it up good by passing a value to count in the initial call.
function Remove-IndexFromList ([int]$Index,[array]$List,[array]$chckd_list=#(),[int]$count=0)
{
if (($list.length+$count-1) -lt $index )
{ throw "the index is out of range" }
$check_item,$temp_list=$list
if ($count -eq $index)
{
$chckd_list+=$temp_list
return $chckd_list
}
else
{
$chckd_list+=$check_item
return (Remove-IndexFromList -count ($count + 1) -index $index -chckd_list $chckd_list -list $temp_list )
}
}
This is a very old question, but the problem is still valid, but none of the answers fit my scenario, so I will suggest another solution.
I my case, I read in an xml configuration file and I want to remove an element from an array.
[xml]$content = get-content $file
$element = $content.PathToArray | Where-Object {$_.name -eq "ElementToRemove" }
$element.ParentNode.RemoveChild($element)
This is very simple and gets the job done.

Resources