Acting on every 100 elements of an array - powershell

I am using Get-ADComputer to return all the computers in Active Directory.
We intend to call the DELL Warranty web-service API and retrieve warranty information for each computer.
The DELL Warranty web-service takes a pipe separated list of service tags, maximum 100, and we have 1483 computers ...
So I need to act on every 100 elements of the array populated by my code, but am having trouble.
clear-host
$Counter=0
$ServiceTag::Empty
$ServiceTagsMasterList=#()
(Get-ADComputer `
-properties OperatingSystem -filter {(operatingsystem -like "*Windows 7*")} `
|Where-Object {$_.name -like "*-*"}`
|Where-Object {$_.name -NotLike "V7-*"})`
| Select -Exp Name|ForEach-Object{
$ServiceTag = $_.substring(3) #remove w7- l7- etc....
if ($ServiceTag.Length -eq "7"){
#write-host "service tag:" $ServiceTag
$ServiceTagsMasterList += $ServiceTag + ','
$Counter+=1
}
}
write-host $ServiceTagsMasterList.length, $Counter, $Counter % 100
write-host $ServiceTagsMasterList
I am having trouble getting the Mod right - I expect $Counter % 100 to return 14 with remainder 83, but the output is 1483 % 100
I need to be able to call the DELL Warranty api for each block of 100 service tags, so I can either do it like that or perhaps split the array?
What's the best approach?

gpunktschmitz already mentioned whats wrong in your code. This is how your batch could look like:
$computer = Get-ADComputer -properties OperatingSystem -filter {(operatingsystem -like "*Windows 7*")} |
Where-Object {$_.name -like "*-*"} |
Where-Object {$_.name -NotLike "V7-*"} |
Select-Object -Expand
for ($i = 0; $i -lt $computer.length; $i+=100)
{
$computer | Select-Object -Skip $i -First 100 | ForEach-Object {
# your code here
}
}
Here an example with your 1483 computers:
$computer = 1 .. 1483 | ForEach-Object { "DellPc$($_)" }
for ($i = 0; $i -lt $computer.length; $i+=100)
{
$computer | Select-Object -Skip $i -First 100 | ForEach-Object {
Write-Host "batch: $i computer: $_"
}
}

you have to write it like this to be calculated:
write-host $ServiceTagsMasterList.length, $Counter, $($Counter % 100)
and % is the remainder not modulo
http://www.madwithpowershell.com/2016/11/powershell-modulus-operator-is.html

Related

Powershell Conditional filter without regex

Im trying to filter ADComputer by Name.
Our naming convention is a follows <CuntryCode>-<Location>-<DeviceType And Number> we have diferent locations in both USA and MX, we also have serval type of devices
Example:
<Device Type>
Servers = S
Desktops = D
Laptops = L
Tablet = T
Routers = R
Switches = U
example of actual naming:
MX-BCN-D002 or US-TAM-L001
Im creating a Script that will look at a remote PC file system, and check if user has a local .PST file. I only want devices that are Type: Desktops and Laptops, but cant seem to create a condition to filter all other devce type
Partial Script:
$Enabled_PC_list =#()
$Enabled_Online_PC_List =#()
# $Enabled_Offline_PC_list =#()
$data = #()
$PCs = Get-ADComputer -filter "Enabled -eq '$true'" -SearchBase "DC=some,DC=domain" -properties name | Select-Object -ExpandProperty name
$Enabled_PC_list += $PCs
foreach($device in $Enabled_PC_list){
Write-Output ">>> testing Device: $device <<<"
if ($device -like "*-*-D*" -or $device -like "*-*-L*" ) {
if(Test-Connection -TargetName $device -Count 1 -Quiet){
$Enabled_Online_PC_List += $device
}
}else{
Write-Output "Device $device not valid "
}
}
}
So with this line if ($device -like "*-*-D*" -or $device.name -like "*-*-L*" ) i was hoping to filter out all devices that matched what im looking for and proceed to do a Test-Connection on those devices .
Do i need to use regex on this ?
How can i use regex in powershell?
Is there a better way ?
You can do this better by letting Active Directory do the filtering for you, instead of filtering from your side. You can generate an LDAP Filter via string manipulation adding an LDAP Clause for each device type:
$filter = '(&(!userAccountControl:1.2.840.113556.1.4.803:=2)(|'
'S', 'D', 'L', 'T', 'R', 'U' | ForEach-Object {
$filter += '(name=*-*-{0}*)' -f $_
}
$filter += '))'
$onlinePCs = Get-ADComputer -LDAPFilter $filter -SearchBase "DC=some,DC=domain" | Where-Object {
Test-Connection -TargetName $_.DNSHostName -Count 1 -Quiet
}
The generated LDAP Filter would look like this (with some formatting):
(&
(!userAccountControl:1.2.840.113556.1.4.803:=2)
(|
(name=*-*-S*)
(name=*-*-D*)
(name=*-*-L*)
(name=*-*-T*)
(name=*-*-R*)
(name=*-*-U*)
)
)
And can be read as, all enabled objects (computers in this case because we're using Get-ADComputer) which Name attribute is like *-*-S* or *-*-D* or *-*-L* and so on.
Reading though your replies, it got me thinking if LDAP can do the filtering, could powershsell do it to? so i tried this and it works
the where clause did the trick Where-Object {$_.Name -like "*-*-D*" -or $_.Name -like "*-*-L*" -or $_.Name -like "*-*-T*"}
$Enabled_Online_PC_List =[System.Collections.ArrayList]#()
$Enabled_Offline_PC_list =[System.Collections.ArrayList]#()
# $data = #()
$searchBase = "OU=USA,DC=some,DC=domain"
$Enabled_PC_list = Get-ADComputer -filter "Enabled -eq '$true'" -SearchBase $searchBase -properties name | Where-Object {$_.Name -like "*-*-D*" -or $_.Name -like "*-*-L*" -or $_.Name -like "*-*-T*"} | Select-Object -ExpandProperty name
foreach($device in $Enabled_PC_list){
# Write-Output "testing now $device"
if(Test-Connection -ComputerName $device -Count 1 -Quiet){
Write-Output " Now Adding $device to Enabled_Online_PC-List"
$Enabled_Online_PC_List.Add($device)
}else{
Write-Output " Now Adding $device to Enabled_Offline_PC_list"
$Enabled_Offline_PC_list.Add($device)
}
}
Write-Output $Enabled_Online_PC_List
so script is working time is cut off dramatically, now i ask again is there a better way? for some reason LDAP does not want to stick in my head

Where object as variable

After reading many of the posted solutions here, none fully applies mine.
This works (returns expected number of instancesNames based on criteria):
$response.result | Where-Object {$_.targetType -eq "webserver" -and ($_.agentHostName -eq "ServerA" -or $_.agentHostName -eq "ServerB")} | select-Object "instanceName"
However, since n number of servers may be found, I created a loop to dynamically create this query:
[System.Text.StringBuilder]$clause = " {`$_.targetType -eq ""webserver"" -and ("
$i = 1;
foreach ($server in $serversArray) {
if ( $i -eq $serversArray.Count ) {
$clause.Append("`$_.agentHostName -eq ""${server}"")}")
} else {
$clause.Append( "`$_.agentHostName -eq ""${server}"" -or ")
}
$i++
}
$clause.Append(" | select-Object ""instanceName""")
$filter = [scriptblock]::Create($clause)
$instances = $response.result | where-object $filter
debugging:
the $clause variable contains:
{$_.targetType -eq "webserver" -and ($_.agentHostName -eq "serverA" -or $_.agentHostName -eq "serverB")} | select-Object "instanceName"
However, it returns all instanceNames (not filtered) instead of the ones that meet the criteria. What am I doing wrong here?
The -in operator would simplify your code. For example:
$response.result |
Where-Object {($_.targetType -eq 'webserver') -and ($_.agentHostName -in $serversArray)} |
Select-Object 'instanceName'

Filter file "yyyy-MM-dd_HH-mm-ss_Computername_Username_File.json" by Computername/Username

I have a folder containing text-files with a standardized naming-scheme like:
2021-03-16_21-25-55_Client1_Edward.Hall_ServerResponse.json
2021-03-16_21-25-33_Client2_Eloise.Glover_ServerResponse.json
2021-03-16_21-17-38_Client3_Millie.Walsh_ServerResponse.json
2021-03-16_21-17-30_Client4_Lilly.Morton_ServerResponse.json
2021-03-16_21-15-45_Client5_Tia.Curtis_ServerResponse.json
2021-03-16_21-15-23_Client1_Edward.Hall_ServerResponse.json
2021-03-16_21-15-10_Client1_Lilly.Morton_ServerResponse.json
2021-03-16_21-15-03_Client2_Eloise.Glover_ServerResponse.json
2021-03-16_21-12-14_Client2_Eloise.Glover_ServerResponse.json
2021-03-16_21-11-25_Client3_Administrator_ServerResponse.json
I want to filter the files and retrieve the latest file (LastWriteTime) of a specific Computername-/Username-combination. Therefore I want to use a code like this:
# $env:COMPUTERNAME = "Client1"
# $env:USERNAME = "Edward.Hall"
$MyFolder = "C:\MyFolder"
Get-ChildItem -Path $MyFolder -File -ErrorAction SilentlyContinue | Where-Object {
$_.Extension -eq ".json" -and $_.COMPUTERNAME -eq $env:COMPUTERNAME -and $_.USERNAME -eq $env:USERNAME
} | Sort-Object -Descending -Property LastWriteTime | Select-Object -First 1
Of course the part -and $_.COMPUTERNAME -eq $env:COMPUTERNAME -and $_.USERNAME -eq $env:USERNAME is NOT working and should only show up the direction to what I imagine.
In the example above the result should be the file "2021-03-16_21-25-55_Client1_Edward.Hall_ServerResponse.json".
I was thinking of using -match, but it should be a exact match -eq.
Could you please help me to find a solution for this?
Thank you very much!
As long as you can count on the name format always conforming to that standard you can just split up the name strings for your required sections:
# $env:COMPUTERNAME = "Client1"
# $env:USERNAME = "Edward.Hall"
$MyFolder = "C:\MyFolder"
Get-ChildItem -Path $MyFolder -File -ErrorAction SilentlyContinue | Where-Object {
($_.Extension -eq ".json") -and ($_.Name.Split('_')[2] -eq $env:COMPUTERNAME) -and ($_.Name.Split('_')[3] -match $env:USERNAME)
} | Sort-Object -Descending -Property LastWriteTime | Select-Object -First 1

How to correct an AD script in power shell

I make this script. This script counts how many users have each OU into the two Principal OU's ( RDS Funcional and VDI Funcional). The script works fine but i need to remove the total count of this two principals OU's. In the Image wants to remove the line RDS Funcional,"723". There an other line with this VDI Funcional,"295"
I need to remove both lines
Also remove the double quotes
Exemple in the image This line is fine -> 4127 Transportes Corporativos,16
Thanks :)
$log = #()
"OU=RDS Funcional,DC=esofitec,DC=loc","OU=VDI Funcional,DC=esofitec,DC=loc" | ForEach-Object {
Get-ADOrganizationalUnit -Filter * -SearchBase $_ | ForEach-Object {
$log += "Processsing {0}" -f $_.distinguishedname
[array] $users = Get-ADUser -Filter "Name -notlike 'test*' -and Name -notlike 'adm*'" -SearchBase $_.distinguishedname
if ($users -ne $null) {
$log += "{0} has {1}" -f $users.name, $users.count
[PSCustomObject]#{
OU = $_.name
Users = "{0}" -f $users.count
}
}
}
} | Export-Csv -Path c:\\ExportUsers.csv -NoTypeInformation -Delimiter ','
$log
Example in that Image:
Example
if you do not want the content of selected OU, but only 1st level children OU :
Get-ADOrganizationalUnit -SearchScope OneLevel -Filter * -SearchBase $_

How to save Active Directory list of all users in all e-mail distributions to .CSV?

I found this example but I am not sure how I can properly save the output to a .csv.
Import-Module ActiveDirectory
$Groups = Get-ADGroup -Filter {GroupCategory -eq "Distribution"} -Properties Members
ForEach ($g in $Groups) {
Write-Host $g.name
Write-Host $g.members `n
}
I have tried something such as:
Import-Module ActiveDirectory
$Groups = Get-ADGroup -Filter {GroupCategory -eq "Distribution"} -Properties Members
ForEach ($g in $Groups) {
$g.name | Export-CSV C:\log.csv -notypeinformation -Append
$g.members | Export-CSV C:\log.csv -notypeinformation -Append
}
It only saves 1 column to the CSV which is called length.
This also makes me remove the 'n at the end of Write-Host $g.members `n
Is there a way that I can grab this data and save it to .csv properly?
UPDATE
With help from TheMadTechnician and this link https://blogs.technet.microsoft.com/heyscriptingguy/2013/07/22/export-user-names-and-proxy-addresses-to-csv-file/ I was able to get closer to what I want.
Import-Module ActiveDirectory
$Groups = Get-ADGroup -Filter {GroupCategory -eq "Distribution"} -Properties Members
ForEach ($g in $Groups) {
$g.name | Export-CSV C:\log.csv -notypeinformation -Append
$g.members | Export-CSV C:\log.csv -notypeinformation -Append
}
$Groups | Select Name,#{L='Members_1'; E={$_.members[0]}}, #{L='Members_2';E={$_.Members[1]}}, #{L='Members_3';E={$_.Members[2]}}, #{L='Members_4';E={$_.Members[3gq]}} | Export-Csv C:\log.csv -notype
This gives me an output of the below in my CSV:
Name Members_1 Members_2 ETC...
NameOfGroup CN=Stormy Daniels,OU=IT,DC=DomainName,DC=com CN=Joe Bob,OU=IT,DC=DomainName,DC=com
Now the list of users can be huge so I would have to continue creating Members_3, Members_4, etc...
I'm not sure if there is a way I can specify all users or loop
#{L='Members_1'; E={$_.members[0]}}
and increment the number until all users are displayed.
I also only need the CN with the name. I don't need the Ou= or Dc=.
Ah this proved harder than I expected - due to the member counting (you have to do a count which can be comparable to integer). I have added a possibility to limit result size as for large queries the active directory produces timeouts.
$limit_result_size = 10
$group_name = Get-ADGroup -Filter {GroupCategory -eq "Distribution"} -Properties Name, Members -ResultSetSize:$limit_result_size | Select-object name
ForEach ($name in $group_name.name) {
If ((![String]::IsNullOrEmpty("$name")) -And ("$name" -notlike 'index')) {
$count_members = Get-ADGroupMember -Identity "$name" | Measure-Object | Select-Object Count
Write-Output "The AD group $name has $($count_members.Count) members.`n"
For($counter = 0; $counter -lt $count_members.Count; $counter++) {
$person = Get-ADGroup -Filter {Name -eq $name} -Properties Name, Members | Select-Object Name, #{N='Members';E={$_.Members[$counter]}}
$person.Members = $person.Members | Select-String 'CN=[0-9a-zA-Z]+' -AllMatches | % { $_.Matches } | % { $_.Value }
$person | export-csv -NoTypeInformation -Append -Path '<your_path>\log.csv'
}
}
}
Short description:
(![String]::IsNullOrEmpty("$name")) -And ("$name" -notlike 'index')) conditions which the AD group should satisfy.
Select-String 'CN=[0-9a-zA-Z]+' -AllMatches | % { $_.Matches } | % { $_.Value } Selects only CN=string_with_numbers. You could replace it with CN=\w+ if you prefer.
The script produces a pair in CV AD group and the CN=user_name. If anything else is unclear please ask.
EDIT
If you have spaces in the names of the Common Names (CN) you have to adjust the regexp to CN=[0-9a-zA-Z\s]+.
EDIT 2 Adding user's email addresses.
Since your question has in the title request for emails I'll answer here without new question. Note that this solution uses lookbehind in regexp to exclude the CN= from the output so it can be used as source for the user query. It also uses a PSCustomObject which gathers all the information together. I have renamed some variables to make better sense in the context of user details.
$limit_result_size = 10
$group_name = Get-ADGroup -Filter {GroupCategory -eq "Distribution"} -Properties Name, Members -ResultSetSize:$limit_result_size | Select-object name
ForEach ($name in $group_name.name) {
If ((![String]::IsNullOrEmpty("$name")) -And ("$name" -notlike 'index')) {
$count_members = Get-ADGroupMember -Identity "$name" | Measure-Object | Select-Object Count
Write-Output "The AD group $name has $($count_members.Count) members.`n"
For($counter = 0; $counter -lt $count_members.Count; $counter++) {
$person = Get-ADGroup -Filter {Name -eq $name} -Properties Name, Members | Select-Object Name, #{N='Members';E={$_.Members[$counter]}}
$person.Members = $person.Members | Select-String '(?<=CN=)[0-9a-zA-Z\s]+' -AllMatches | % { $_.Matches } | % { $_.Value }
$person_details = Get-AdUser -filter {name -eq $member} -Properties mail | Select-Object mail
$person_additional_details = [PSCustomObject]#{ group_name = $group.Name
user_name = $group.Members
email = $person_details.mail
}
If ([String]::IsNullOrEmpty($($person_additional_details.email))) {
$person_additional_details.psobject.properties["email"].value = '<empty>'
}
# For user to see the written data
Write-Output "AD Group: $($person_additional_details.group_name) `
AD User: $($person_additional_details.user_name) `
Users`'s email: $($person_additional_details.email)`n"
# writing into the CSV file
$person_additional_details | export-csv -NoTypeInformation -Append -Path '<your_path>\log.csv'
}
}
}