Trouble with $arrayList.Remove($item) - powershell

I am having trouble with this.
What i am trying to do is I have 2 different array, and I am trying to exclude $items in $ignore from $users
I can't use where $users | where {$ignore -notcontains $_} because the string in the $ignore array may contain wildcard (e.g., admin*)
But here is the main problem, when I matched the $item that i want to remove, it removes my entire array instead of the selected value.
[System.Collections.ArrayList]$users = Get-ADUser -Filter * | Select Name
$users = $users.name
$ignore = $audArr[5] -Split(",")
foreach ($item in $ignore) {
if ($item.Contains("*")) {
foreach ($user in $users) {
$matchResult = $user -like $item
if ($matchResult -eq $true){
$users = $users.Remove($item)
}
}
}
else {
$users = $users.Remove($item)
}
}
However, I if i try to .Add it works despite showing error
Cannot convert the "3" value of type "System.Int32" to type "System.Collections.ArrayList".
At C:\users\administrator\desktop\scannerTest.ps1:326 char:17
+ $users = $users.Add($item)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [], RuntimeException
+ FullyQualifiedErrorId : ConvertToFinalInvalidCastException
Also, since i am already asking about powershell items.. Does anyone know what how to get LogonTime, KickoffTime via powershell? Adn what is ACB?

Santiago Squarzon's helpful answer uses a good approach with regex matching. We can further enhance the regex matching to include a combination of wildcard and exact matches.
$users = 'John Doe','Wolverine','Mr. Pants','Administrator','Jane Doe','Master Administrator'
# two exact matches and one wildcard match
$ignore = 'john doe','jane doe','admin*'
$regex = '^({0})$' -f (($ignore -replace '\*','.*') -join '|')
# List users who don't match $ignore
$users -notmatch $regex # Jane Doe, John Doe, Administrator not listed
If you must use an ArrayList, you can apply the same technique as above by
removing successful matches:
[collections.ArrayList]$users = 'John Doe','Wolverine','Mr. Pants','Administrator','Jane Doe','Master Administrator'
# two exact matches and one wildcard match
$ignore = 'john doe','jane doe','admin*'
$regex = '^({0})$' -f (($ignore -replace '\*','.*') -join '|')
$users -match $regex | Foreach-Object {
[void]$users.Remove($_)
}
Explanation:
In regex, alternations (basic OR logic) are separated by |. .* greedily matches any number of characters in a string. ^ denotes the beginning of a string. $ denotes the end of a string. Putting that all together, your exact matches would look like ^exact$ and your partial matches would look like ^admin.*$ (for admin*) or ^.*admin.*$ (for *admin*). Using alternation, you can combine those matches for an OR effect with ^(admin.*|Benjamin Dover|Johnny Lawrence)$.

What you're trying to do could be simplified to something like this:
Create a test case, the array will have User00 to User10 plus Administrators and Domain Admins.
$ignore = #(
'Administrators'
'Domain Admins'
'User05'
'User10'
)
[System.Collections.ArrayList]$users = 0..10 |
ForEach-Object { [pscustomobject]#{Name="User{0:00}" -f $_} }
$users.Add([pscustomobject]#{Name="Administrators"})
$users.Add([pscustomobject]#{Name="Domain Admins"})
Now you can simply use the .Where(..) method filtering those elements of the $users array that do not match the $ignore array on one side and those that do match on the other side.
$toIgnore = $ignore -join '|'
$notIgnoredUsers, $ignoredUsers = $users.Where({$_.Name -notmatch $toIgnore},'Split')
$notIgnoredUsers
Name
----
User00
User01
User02
User03
User04
User06
User07
User08
User09
$ignoredUsers
Name
----
User05
User10
Administrators
Domain Admins
As for how you're using the .Remove(..) method, since it doesn't produce any output, by doing $users = $users.Remove($item) you're actually setting the variable to null:
PS /> [System.Collections.ArrayList]$arr = 'one','two','three'
PS /> $arr = $arr.Remove('one')
PS /> $null -eq $arr
True
If you want to remove one from the list you would simply do $arr.Remove('one').

Related

There are spaces in string, but I can't split it in powershell

I want to compare user's groups and DFS's groups to find out if the user has access authority to DFS. but I got stuck in split string.
here is part of my code:
$folder = Read-Host 'Enter the folder path'
$User = Read-Host 'Enter the sAMAccountName of user'
$getSGs = (get-acl $folder).Access | Select-Object -uniq -ExpandProperty IdentityReference
$getSGsSplit = $getSGs -split('.*\\')
$arr = $getSGsSplit -split ' '
Write-Host $arr
the value of $getSGs:
domain-ORG\xxx-Read domain-ORG\xxx-Modify domain-ORG\xxx-Admin domain-ORG\xxx-Center domain-ORG\xxxx-Admin BUILTIN\Administrators
the value of $getSGsSplit:
xxx-Read xxx-Modify xxx-Admin xxx-Center xxxx-Admin Administrators
What I Want is to split the $getSGsSplit by spaces:
xxx-Read
xxx-Modify
xxx-Admin
xxx-Center
xxxx-Admin
BUILTIN\Administrators
BUT I have been tried lots of patterns, all of them doesn't work.
$getSGsSplit -split ' .-`t`n`r'
$getSGsSplit -replace ' ','\n'
$getSGsSplit.split(' ').trim()
$getSGsSplit.split('\s').trim()
$getSGsSplit -split ' '
And no matter which pattern I used , the console of write-host $arr[2] still were spaces. And console of write-host $arr[2].getType() always were System.String
PS C:\Users\zzz\Desktop> C:\Users\zzz\Desktop\12.ps1
Enter the folder path: \\domain.org\xxx\xx\xxx
Enter the sAMAccountName of user: zzz
PS C:\Users\zzz\Desktop>
Can anyone advise how to resolve this?
Why not simply loop over the values returned by Get-Acl and replace the domain part on every item?
Something like
((Get-Acl $folder).Access |
Select-Object IdentityReference -Unique).IdentityReference |
ForEach-Object { $_ -replace '^domain-ORG\\' }
P.S.
Your code $getSGs = (get-acl $folder).Access | Select-Object -uniq -ExpandProperty IdentityReference does not return a string, but an array of objects with a Value property in which the account name is held.
Because you later use -split('.*\\') on it, the result becomes an array of strings, but if you simply leave that out the code can be much simpler.
Just considering the string that you already have:
Below should give you what you are looking for:
$a= "domain-ORG\xxx-Read domain-ORG\xxx-Modify domain-ORG\xxx-Admin domain-ORG\xxx-Center domain-ORG\xxxx-Admin BUILTIN\Administrators"
($a -split '\s').replace('domain-ORG\','')

How to convert the data from a wildcard into a string

I am trying to trim down strings located inside of a .csv so that they only contain the data I need. An example string might be "\path\folder\folder\folder\folder Owner = John Doe".
I just want the John doe part.
Currently I'm importing a list of about 6000 of these strings some have the line some don't.
What I'm trying is:
$owners = Import-Csv .\Owners.csv
ForEach($owner in $owners){
if ($owner.Description -like "= *") {$owner.Description = "*"}
}
This doesn't seem to work. Is it possible to save the wildcard as a variable?
One method would be to use a Regular Expression rather than Wildcard globbing.
$owners = Import-Csv .\Owners.csv
$output = foreach ($owner in $owners) {
# The Description Property has an equal sign followed by a space
if ($owner.Description -like '*= *') {
# Use a regex then capture the the text after the equals and space, replace the property with that capture group.
$owner.Description = $owner.Description -replace '.*=\s(.*)$', '$1'
$owner
}
}
$output | Export-Csv .\Owners.update.csv -NoTypeInformation
You can do it with regex and match:
CSV:
Description
\path\folder\folder\folder\folder Owner = John Doe
\path\folder\folder\folder\folder Owner = Captain Black
Code:
$owners = Import-Csv .\Owners.csv
ForEach($owner in $owners){
$match = $owner.Description -match "= .*"
if ($match){
$owner.Description = $matches[0] -replace "= ", ""
}
echo $owner
}
Output:
Description
-----------
John Doe
Captain Black

Function to generate samaccountname in PowerShell

I am trying to write a PS function that can generate SamAccountName in a specific way, I have found another question here but it is not similar to what i'm trying to do.
I need the SamAccountName to be generated like this:
UNIT + initial_of_firstName + initial_of_lastName
for example employee Jane Doe in unit FLN could have FLNjd as SamAccountName, and the function should check if that ID is taken by another user and if true then SamAccountName should be:
UNIT + initial_of_firstName + first_two_initials_of_lastName such as FLNjdo
and if that is also taken then SamAccountName should be:
UNIT + first_two_initials_of_firstName + first_two_initials_of_lastName such as FLNjado
and if that is also taken then SamAccountName should be:
UNIT + first_two_initials_of_firstName + first_two_initials_of_lastName + 1 such as FLNjado1
and from here it starts adding numbers 2, 3, 4.... as long as the function finds that the ID exists.
I only managed to extract the initials needed:
$first_of_fname = ($_.Fname).Substring(0,1).ToLower()
$first_of_lname = ($_.Lname).Substring(0,1).ToLower()
$FirstandSecond_of_fname = ($_.Fname).Substring(0,2).ToLower()
$FirstandSecond_of_lname = ($_.Lname).Substring(0,2).ToLower()
I need now to know how to generate the the SamAccountName in the above form and order.
ANy help will be very much appreciated.
You can do the following:
Function Get-SamAccountName {
Param(
[Parameter(Position=0,ValueFromPipelineByPropertyName)]
$Unit,
[Parameter(Position=1,ValueFromPipelineByPropertyName)]
$First,
[Parameter(Position=2,ValueFromPipelineByPropertyName)]
$Last
)
$fcount = 1
$lcount = 1
$inc = 1
$Sam = "{0}{1}{2}" -f $unit,$first.substring(0,$fcount).ToLower(),$last.substring(0,$lcount++).ToLower()
while (Get-AdUser -Filter "SamAccountName -eq '$Sam'") {
if ($fcount -le 2) {
$Sam = "{0}{1}{2}" -f $unit,$first.substring(0,$fcount++).ToLower(),$last.substring(0,$lcount).ToLower()
} else {
$Sam = "{0}{1}{2}{3}" -f $unit,$first.substring(0,2).ToLower(),$last.substring(0,2).ToLower(),$inc++
}
}
$Sam
}
Usage Option 1: Using Named Parameters
Get-SamAccountName -Unit 'FLN' -First 'Sam' -Last 'Wise'
Usage Option 2: Using positional parameters (unnamed)
Get-SamAccountName 'FLN' 'Sam' 'Wise'
Usage Option 3: Having A Text List Of Units and Names
$users = Get-Content users.txt
$users
FLN,Sam,Wise
HRS,John,Doe
COM,Jane,Doe
$users | ConvertFrom-Csv -Header 'Unit','First','Last' | Foreach {
$_ | Get-SamAccountName
}
The pipe to ConvertFrom-Csv can be skipped here if your file is already a CSV with the proper headers.
Explanation:
The function returns a string that represents an available SamAccountName value.
$fcount is the first name character count that we want to retrieve. $lcount is the last name character count that we want to retrieve. $inc is the number we want to append to the username if needed.
$var++ notation increments the $var value by one after its line is executed. The timing of $fcount++ and $lcount++ allows us to retrieve (1,1),(1,2),(2,2) characters from (first,last) while minimizing code. $inc++ will increment after the current $inc value is retrieved.
-f is the string format operator.
The while loop will only execute if the current created SamAccountName value is found.
Note: The code shortening complexity may or may not be worth it. It does not gain much from a performance perspective, but I do feel code complexity on this level is merely relative.
To take a different approach, instead of multiple if statements, you can use the switch statement "backwards" to find out what is $null or not available, and fall back to the default switch which is the final while iteration loop incrementing the count.
#Get substrings
$first_of_fname = ($_.Fname).Substring(0,1).ToLower()
$first_of_lname = ($_.Lname).Substring(0,1).ToLower()
$FirstandSecond_of_fname = ($_.Fname).Substring(0,2).ToLower()
$FirstandSecond_of_lname = ($_.Lname).Substring(0,2).ToLower()
#create patters to match on
$pattern1 = $UNIT + $first_of_fname + $first_of_lname
$pattern2 = $UNIT + $first_of_fname + $FirstandSecond_of_lname
$pattern3 = $UNIT + $FirstandSecond_of_fname + $FirstandSecond_of_lname
#evaluate what is available
switch($null){
(Get-ADUser -Filter "samAccountName -like '$pattern1'"){
Write-Host "$pattern1 - Free"
break
}
(Get-ADUser -Filter "samAccountName -like '$pattern2'"){
Write-Host "$pattern2 - Free"
break
}
(Get-ADUser -Filter "samAccountName -like '$pattern3'"){
Write-Host "$pattern3 - Free"
break
}
default {
$cnt = 1
while (Get-ADUser -Filter "SamAccountName -like '$pattern3$cnt'") {
$cnt++
}
Write-Host "$pattern3$cnt - Free"
break
}
}

How do I isolate an AD Distinguished Name, and get rid of extra lines?

I'm trying to print just the first level(?) distinguished name of a person's AD group. A lot of people are part of many groups, and so far, with the groups listed on multiple lines, I've used -replace to get rid of everything except the thing I want.
As it stands, let's say these are the groups John Smith is in:
PS C:\> $member = Get-ADUser jsmith -prop MemberOf | Select Name,MemberOf
$member.Name
$member.MemberOf
Smith, John A - (jsmith)
CN=VPN Users,OU=Resource,OU=User Groups,OU=Groups,OU=Delegated OUs,OU=Delegation,DC=domainetc
CN=ResG-Smith,OU=Smith,OU=Research,OU=User Groups,OU=Groups,OU=Delegated OUs,OU=Delegation,DC=domainetc
CN=Faculty,OU=Business,OU=User Groups,OU=Groups,OU=Delegated OUs,OU=Delegation,DC=domainetc
My end goal is something like this:
PS C:\> Get-ResearchGroup jsmith
Smith, John A - (jsmith)
Smith
Or maybe even:
Name MemberOf
---- --------
Smith, John A - (jsmith) Smith
(maybe with some Add-Member jazz, which I'm pretty sure I can figure out on my own)
Anyway, the point is to get 'Smith' by itself from CN=ResG-Smith,OU=Smith,OU=Research,OU=User Groups,OU=Groups,OU=Delegated OUs,OU=Delegation,DC=domainetc
Now, our research groups (ResG) are all named the same thanks to a script, I believe, but it would be preferable to replace to get 'Smith' from OU=Smith rather than CN=ResG-Smith, if that's doable.
Function Get-ResearchGroup {
[CmdletBinding()]
Param(
[Parameter(Position=1)]
[string[]]$Users,
[switch]$Raw
)
Foreach ($user in $Users) {
$member = Get-ADUser $user -prop MemberOf | Select Name,MemberOf
# $member
#printing out the variable right now results as follows:
# Name MemberOf
# ---- --------
# Smith, John A - (jsmith) {CN=VPN Users,OU=Resource,OU=User Groups,OU=Groups,OU=Delegated OUs,OU=Delegation,DC=domainetc, CN=ResG-Smith,OU=Smith,OU=Research,OU=User Groups,OU=Groups,OU=Delegated O...
#I still don't know a whole lot about objects yet, but I wasn't too surprised when -replace didn't work on my variable. I tried this:
# $member -replace '.*ResG-Smith,OU=' -replace ',OU=Research,.*'
#results:
# #{MemberOf=Microsoft.ActiveDirectory.Management.ADPropertyValueCollection}
#(and, sure, maybe I just set something up wrong)
#Now, this kind of works:
# $member.MemberOf -replace '.*CN=ResG-Smith,OU=' -replace ',OU=Research,.*'
# CN=VPN Users,OU=Resource,OU=User Groups,OU=Groups,OU=Delegated OUs,OU=Delegation,DC=domainetc
# Smith
# CN=Faculty,OU=Business,OU=User Groups,OU=Groups,OU=Delegated OUs,OU=Delegation,DC=domainetc
#Except only on the applicable line.
#This is pretty much where I'm at right now. Everything below the stackoverflow link are my failed attempts/additions.
$member.Name
$MemberOf = $member.memberof -replace '.*CN=ResG-' -replace ',OU=Research,.*' -replace '.*OU=' -replace 'Delegation,.*' -replace 'Enterprise,.*' -replace 'Groups,.*'
$MemberOf | where { $_ -ne "" }
#https://stackoverflow.com/questions/19168475/powershell-to-remove-text-from-a-string
#I tried some different variations of adding each of these (never more than one at a time) on to the chain of replaces, with no success:
# -replace " `r`n"
# -replace " `\r`\n"
# -replace " \\r\\n"
# -replace "`r`n"
# -replace "`\r`\n"
# -replace "\\r\\n"
#https://stackoverflow.com/questions/22238465/removing-line-break-powershell
#I've also tried a few variations of .replace(), which didn't work (same results)
#https://community.spiceworks.com/topic/825700-get-aduser-distinguishedname-powershell-help
# Get-ADUser jsmith -prop MemberOf | Select Name,MemberOf,#{l='OU';e={$_.DistinguishedName.split(',')[1].split('=')[1]}
# The hash literal was incomplete.
# + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
# + FullyQualifiedErrorId : IncompleteHashLiteral
#The next code look pretty similar to above, but there appear to be some differences, so I thought I'd try it.
#source: https://www.experts-exchange.com/questions/28206442/Get-part-of-Distinguished-Name-and-Output-to-file-in-Powershell.html
# Get-ADUser jsmith -prop MemberOf | Select Name,MemberOf,#{name="Research";expression={($_.DistinguishedName -split ",OU=")[1]}}
# Name MemberOf
# ---- --------
# Smith, John A - (jsmith) {CN=VPN Users,OU=Resource,OU=User Groups,OU=Groups,OU=Delegated OUs,OU=Delegation,DC=domainetc, CN=ResG-Smith,OU=Smith,OU=Research,OU=User Groups,OU=Groups,OU=Delegated OUs,OU=Delegat...
}
}
Another way to do this without parsing the DistinguishedName with string methods would be to do a Get-ADGroup lookup. It's less efficient with the second lookup, but less likely to break on unexpected directory paths.
function Get-ResearchGroup {
param (
[string[]]$Identity
)
foreach ($ID in $Identity) {
$User = Get-ADUser $ID -Properties MemberOf
$ResearchGroupsDN = $User.MemberOf | Where-Object {$_ -like 'CN=ResG*'}
foreach ($ResearchGroupDN in $ResearchGroupsDN) {
$ResearchGroupObject = Get-ADGroup $ResearchGroupDN
[PSCustomObject]#{
'Name' = $User.Name
'MemberOf' = $ResearchGroupObject.Name -replace 'ResG',''
}
}
}
}
You can use the "split" method twice to first split the string into an array by commas, then you can "split" again to separate the "OU=" from everything else. Your Get-ADUser command will end up looking similar to this:
Get-ADUser $user | Select-Object Name, Memberof, #{l='OU';e={$_.DistinguishedName.split(',')[1].split('=')[1]}}

Extracting a portion of a string then using it to match with other strings in Powershell

I previously asked for assistance parsing a text file and have been using this code for my script:
import-csv $File -header Tag,Date,Value|
Where {$_.Tag -notmatch '(_His_|_Manual$)'}|
Select-Object *,#{Name='Building';Expression={"{0} {1}" -f $($_.Tag -split '_')[1..2]}}|
Format-table -Groupby Building -Property Tag,Date,Value
I've realized since then that, while the code filters out any tags containing _His or _Manual, I need to also filter any tags associated with _Manual. For example, the following tags are present in my text file:
L01_B111_BuildingName1_MainElectric_111A01ME_ALC,13-Apr-17 08:45,64075
L01_B111_BuildingName1_MainElectric_111A01ME_Cleansed,13-Apr-17 08:45,64075
L01_B111_BuildingName1_MainElectric_111A01ME_Consumption,13-Apr-17 08:45,10.4
L01_B333_BuildingName3_MainWater_333E02MW_Manual,1-Dec-16 18:00:00,4.380384E+07
L01_B333_BuildingName3_MainWater_333E02MW_Cleansed,1-Dec-16 18:00:00,4.380384E+07
L01_B333_BuildingName3_MainWater_333E02MW_Consumption,1-Dec-16 18:00:00,25.36
The 333E02MW_Manual string would be excluded using my current code, but how could I also exclude 333E02MW_Cleansed and 333E02MW_Consumption? I feel I would need something that will allow me to extract the 8-digit code before each _Manual instance and then use it to find any other strings with a {MatchingCode}
xxx_xxxx_xxxxxxxxxxx_xxxxxxxxxx_MatchingCode_Cleansed
xxx_xxxx_xxxxxxxxxxx_xxxxxxxxxx_MatchingCode_Consumption
I know there are the -like -contains and -match operators and I've seen these posts on using substrings and regex, but how could I extract the MatchingCode to actually have something to match to? This post seems to come closest to my goal, but I'm not sure how to apply it to PowerShell.
You can find every tag that ends with _Manual and create a regex pattern that matches any of the parts before _Manual. Ex.
$Data = Import-Csv -Path $File -Header Tag,Date,Value
#Create regex that matches any prefixes that has a manual row (matches using the value before _Manual)
$ExcludeManualPattern = ($Data | Foreach-Object { if($_.Tag -match '^(.*?)_Manual$') { [regex]::Escape($Matches[1]) } }) -join '|'
$Data | Where-Object { $_.Tag -notmatch '_His_' -and $_.Tag -notmatch $ExcludeManualPattern } |
Select-Object -Property *,#{Name='Building';Expression={"{0} {1}" -f $($_.Tag -split '_')[1..2]}}|
Format-table -GroupBy Building -Property Tag,Date,Value