Store AD Computer Names in an Array - powershell

A fairly basic script - searches Ou in AD and exports computers - I want to store each computer into an array so I can loop through later and run some commands against the computers. Although not having much luck with the array itself - am I completely off the track here?
$computers = #()
$i = 0
$ou = [ADSI]"LDAP://OU=Domain Controllers,DC=test,DC=local"
foreach ($child in $ou.psbase.Children) {
if ($child.ObjectCategory -like '*computer*') {
Write-Host $child.Name
$computers[$i] = $child.name
}
$i += 1
}

You're indexing into an empty array with $computer[$i]. If you don't know how big the array should be but you know it won't be huge, change to this:
$computers += $child.Name
If you know the size then allocate the array that size like so:
$computers = new-object string[] $arraySize
Then you can index into the array up to size - 1.
If you don't know the size and think it will be large, use a list instead e.g.:
$computers = new-object system.collections.arraylist
[void]$computers.Add($child.Name)

Use a pipeline, filter the objects with Where-Object (alias ?), and output the names in a ForEach-Object loop (alias %). The result is either an array of names or $null (when the LDAP query did not return any computer objects).
$ou = [ADSI]"LDAP://OU=Domain Controllers,DC=test,DC=local"
$computers = $ou.PSBase.Children |
Where-Object { $_.ObjectCategory -like '*computer*' } |
ForEach-Object { $_.Name }
If you want the result to be an empty array when no computer objects are found, wrap the command pipeline in #():
$computers = #($ou.PSBase.Children | ...)

Related

PowerShell Compare AD Groups but show only these what I need

I have a PowerShell script that is generating from a file.txt a list of users and groups where they belong to.
The next step that I need to achieve is to confirm if the group from file Groups.txt is assigned to users.
To do this I used logical operator -contains so the code is looking like that:
$UserList = Get-Content ("C:\users.txt")
$GroupList = Get-Content ("C:\Groups.txt")
$result = #()
foreach ($UserList in $UserList){
$data = New-Object PSObject
$Group = (Get-ADPrincipalGroupMembership -Identity $UserList | foreach {$_.SamAccountName}) -contains $GroupList
$data = get-aduser $Userlist -properties samaccountname,givenname,surname | select samaccountname,givenname,surname, #{name="Groups";expression={$Group}}
$result += $data
}
$result
The code is working when I have only one group in file Groups.txt. If I have two or more it is applying only the last one with value True
The resolution what I expecting is
When the user has one or multiple groups from the file Group.txt script should mention that group name and nothing else.
To be more precise I need something like that as results:
samaccountname givenname surname Groups
-------------- --------- ------- ------
User FirstName Surname False (or anything)
User1 Firstname Surname Group1, Group2, Group3
Many thanks for any help in this matter.
You can do the following:
$UserList = Get-Content "C:\users.txt"
$GroupList = Get-Content "C:\Groups.txt"
$result = foreach ($User in $UserList) {
$Groups = Get-ADPrincipalGroupMembership -Identity $User |
Where SamAccountName -in $GroupList
Get-ADUser -Identity $User -Properties GivenName,Surname |
Select-Object SamAccountName,GivenName,Surname,#{name="Groups";expression={$Groups.SamAccountName}}
}
$result
When using a foreach loop, the proper syntax is foreach ($<item> in $<collection>) { statements }. $<item> is just a variable that you can reference within the statements, and it should be a variable that has not been assigned up until that point. See About_Foreach.
If your foreach statements produce output as in this case, they can simply be collected by assigning a variable to the foreach loop. This will result in a more efficient array assignment. Using += to effectively expand an array, just results in creating a new array on each loop iteration that is bigger than the previous. It is not efficient and is unnecessary in cases like these.
Regarding collection comparison, -contains is used when comparing a left-hand side (LHS) single item with a right-hand side (RHS) collection. A sample syntax would be $<collection> -contains $<single_item>. -in is used when comparing a LHS single item with a RHS collection. A sample syntax would be $<single_item> -in $<collection>. See About Comparison Operators.
Since you ultimately wanted to gather groups from a command output based on a certain condition, that is a prime candidate for Where or Where-Object. It's pseudo code usage is out of these 20 items, show me the ones that meet a certain condition. See Where-Object.

How to remove characters from an item in an array using PowerShell

I have an array of computer names, $computers, that are in a FQDN format. I want to trim all characters to the right of the first period and including the period.
Ex: server-01.mydomain.int = server-01
This is what I tried but it errors out.
$computers = Get-VM | Select Name
$computers = $computers.Substring(0, $computers.IndexOf('.'))
$computers
When you do |Select Name, PowerShell returns an object with a Name property, rather than only the value of each Name property of the input objects.
You could change it to Select -ExpandProperty Name and then iterate over each item in the array for the actual substring operation using a loop or ForEach-Object - although we can skip the first step completely:
$computers = Get-VM |ForEach-Object { $_.Name.Substring(0, $_.Name.Indexof('.')) }
Or another way.
$computers = Get-VM | ForEach-Object { ($_.Name -split ".")[0] }
Since you are always selecting the first string before the first "." you can just split at the dot and select the first element in the resulting array.

Cross-matching username field from csv to samaccount name in Active Directory? + unique filter?

I was recently given a list of 60k usernames. I was then asked to find these usernames within active directory, list their display name, and keep them in separate CSV files based on their affiliate (extensionattribute7).
I've been fumbling around with this for a good 3 hours.. There's not many resources available around here to help me, and i'm getting desperate. Thank you for taking the time to help me out!
I haven't had a chance to test this but here is a quick script that will do what you need.
Essentially, the script enumerates your csv file of usernames and will build an array with all of the matching users within your csv. In addition using 'extensionattribute7', it will build a unique list of affiliates within the $aAffiliates array.
Next the script enumerates the unique affiliates and for each one will find all of the users who's affiliate property matches the current affiliate in the loop. These found users are then added to the $aResults array which are then exported to a csv file that contains the affiliate name in the file name.
Hope this helps!
Import-Module ActiveDirectory
$aUsers = #()
$aAffiliates = #()
$List = Get-Content "C:\Temp\List.txt"
ForEach ($Item in $List) {
$Item = $Item.Trim()
$User = Get-ADUser -Filter { SamAccountName -like $Item } -Properties DisplayName, extensionattribute7
# Build unique list of affiliates
If ($aAffiliates -notcontains $User.extensionattribute7) {
$aAffiliates += $User.extensionattribute7
}
$hItemDetails = New-Object -TypeName psobject -Property #{
Username = $Item
DisplayName = $User.DisplayName
Affiliate = $User.extensionattribute7
}
#Add data to array
$aUsers += $hItemDetails
}
ForEach ($Affiliate in $aAffiliates) {
$aResults = #()
ForEach ($User in $aUsers) {
If ($User.Affiliate -eq $Affiliate) {
$aResults += $User
}
}
$Path = "C:\Temp\Results-$($Affiliate).csv"
$aResults | Export-CSV $Path
}

Trouble executing powershell script on multiple remote machines

I need to generate a list of all users on our network who are members of their workstation's local administrators group. I found a script here https://gallery.technet.microsoft.com/scriptcenter/List-local-group-members-762b48c5#content which was written to list local group members by executing a WMI query through Powershell. I've tested this script and it works well, but I've been trying to modify it to take in a list of computers to check and that's where I've run into trouble. Here's what I've done:
function LocalAdmins
{
param([string]$GroupName = "Administrators")
begin
{
# Get all workstations listed in this text file
$WorkStations = Get-Content -Path C:\useful_lists\testLocal.txt
# Initialize an array to hold the results of the query
$arr = #()
# hash table for storing computer name, member pairings
$hash = #();
}
process
{
foreach ($machine in $WorkStations)
{
$wmi = Get-WmiObject -ComputerName $machine -Query `
"SELECT * FROM Win32_GroupUser WHERE GroupComponent=`"Win32_Group.Domain='$machine',Name='$GroupName'`""
# Parse out the username from each result and append it to the array
if ($wmi -ne $null)
{
foreach($item in $wmi)
{
$arr += ($item.PartComponent.Substring($item.PartComponent.IndexOf(',') + 1).Replace('Name=', '').Replace("`"", ''))
}
}
# Return a hash table comprised of two columns: Computer Name & Members
$hash += #{ComputerName=$machine;Members=$arr}
}
}
end
{
return $hash
}
}
When I ran the unmodified script here's what I got as output:
PS > (Get-LocalGroupMembers -ComputerName "<COMPUTER NAME>" -GroupName "Administrators").Members
ACCOUNTNAME
ACCOUNTNAME
ACCOUNTNAME
PS >
However, when I run the version of this script that I modified I get this:
PS > (LocalAdmins -GroupName "Administrators").Members
PS >
I'm fairly certain that the issue lies either in how I've setup the first foreach loop to run the wmi query or how the results of that query are being stored in the hash table. I'm not sure what I could do differently to fix the issue.
Thanks in advance to anyone who can help!
UPDATE
Per mortenya's suggestion, I edited my test text file to only include one computer in it. Doing so, along with taking out the foreach ($machine in $computers) loop worked as expected producing the following result:
>> LocalAdmins -GroupName "Administrators"
Name Value
---- ----
ComputerName {computerName.domain}
Members {account, account, account, account}
>>
However, going back and trying to get this to work when incorporating multiple machines using the code above (I've updated it since my initial post), I get the following:
>> LocalAdmins -GroupName "Administrators"
Name Value
---- -----
ComputerName computerName1.domain
Members {}
ComputerName computerName2.domain
Members {}
>>
Why is it that with one machine in the list I can get the members of the Administrator group, but adding a second computer to the list makes it so I can not retrieve members from that group on either machine?
So, if you're going to use Begin{}, Process{}, and End{}, use them for what they're meant for, in the Begin{} block, initialize all your arrays and constant varaibles.
Begin {
# Get all workstations listed in this text file
$WorkStations = Get-Content -Path C:\useful_lists\testLocal.txt
# Store the contents of that list in an array
$computers = #()
$hash = #()
}
Outside of that, I did this same thing a few months ago, it's a little messy, but it spit out a list of computers and who was in the Local Administrators group. It was partially to practice some different methods.
$output = 'c:\psresults\ListOfLocalAdministratorsGroup.txt'
$results = New-Object System.Collections.ArrayList
$objSID = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")
$objgroup = $objSID.Translate( [System.Security.Principal.NTAccount])
$objgroupname = ($objgroup.Value).Split("\")[1]
foreach($server in (Get-ADComputer -Filter *).name)
{
$admins = New-Object System.Collections.ArrayList
$group =[ADSI]"WinNT://$server/$objgroupname"
$members = #($group.psbase.Invoke("Members"))
$members | foreach {
$obj = new-object psobject -Property #{
Server = $Server
Admin = $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
}
#$obj
$admins.Add($obj)
}
$results.Add($admins)
}
$results | Out-File $Output
I found the meat of that somewhere and then modified it a bit.
EDIT: I just put this into ISE and it seems to work fine
$machine = "testsrv"
$groupname = "Administrators"
$wmi = Get-WmiObject -ComputerName $machine -Query `
"SELECT * FROM Win32_GroupUser WHERE GroupComponent=`"Win32_Group.Domain='$machine',Name='$GroupName'`""
if ($wmi -ne $null)
{
foreach ($item in $wmi)
{
$arr += ($item.PartComponent.Substring($item.PartComponent.IndexOf(',') + 1).Replace('Name=', '').Replace("`"", ''))
}
}
$hash = #{ComputerName=$machine;Members=$arr}
return $hash
Get it working on one machine, then start trying to add the loops back in.
EDIT 2.0:
I made a .txt file with only computer names in it, not the FQDN, that works fine for me. I can run it and get results using your script with minor modification.
Despite what I'd said about the Begin{} block, the $arr variable will need to be initialized inside the foreach ($machine in $WorkStations) loop. The reason for this is that when the loop runs, it will create the $arr array, add the data we want, insert that data into a global variable, and then clean up the $arr variable. If we make this global, it won't be cleaned up until the function is done, and we will just keep adding to it, which isn't what we actually want in this case.
The problem you're having with getting multiple machines to work is likely how you're building your results table.

Powershell -- Exporting to CSV based on Selected Objects from an array

Sorry for the wierd title, I didn't know how to phrase the question. I'm relatively new to Powershell and I'm writing a program. Basically, I have an array where the user has selected settings or wish to have "Selected" from the GWMI query stored in "$settings_array". I want to output the results to a CSV. When I try to run it, only the first Select statement gets output to the CSV. Output to the textbox works fine. I know it has something to do with how it's being stored in the array at each ieration. $resultList is intialized as an array ($resultList = #()). There are hundreds of lines of code for the Form and other functions, but here is the relevant code. Thanks for the help! Let me know if I need to post more of the code.
$colItems = GWMI Win32_NetworkAdapterConfiguration -computername $PCname -namespace "root\CimV2" -filter "IpEnabled = TRUE"
ForEach ($objItem in $colItems)
{
ForEach ($objItem2 in $settings_array)
{
$resultList += $objItem | Select $objItem2
$richTextBox1.Appendtext("$objItem2" + ": " + $objItem.$objItem2 + "`r`n")
$richtextbox1.ScrollToCaret()
}
}
$resultList | export-csv "$ScriptDir\Exports\$Outputfile"
CSV is made with rows and columns. Each object in the array you export gets a row, and each object gets a column value for each property. You add a new record/object to the resultlist with a single property every time(every object has only one property). The reason you only get the first is because your records contain different property-names. To solve this "non-static propertyname" problem, powershell takes the first object's properties as a template for the csv file. Since object2,object3 etc. doesn't include the same property, they will be blank. However, when you hit another object with the same property as the first object had, the value will be included too. Ex. you get the Name property for all network adapters, but blank values on the rest.
Your sample is missing information, ex. how $settings_array is built. If it's a normal string-array like:
$settings_array = "Name", "DisplayName", "Test"
or
$settings_array = #("Name", "DisplayName", "Test")
Then you can pass the whole array to select.
$colItems = GWMI Win32_NetworkAdapterConfiguration -computername $PCname -namespace "root\CimV2" -filter "IpEnabled = TRUE"
ForEach ($objItem in $colItems)
{
#Write to screen
ForEach ($objItem2 in $settings_array)
{
$richTextBox1.Appendtext("$objItem2" + ": " + $objItem.$objItem2 + "`r`n")
$richtextbox1.ScrollToCaret()
}
}
#Save to CSV
$colItems | Select-Object -Property $settings_array | export-csv "$ScriptDir\Exports\$Outputfile"
Notice the last line. Now, the foreach loop is only used for your textbox-content, while the last line formats the CSV as it should.
EDIT Try this to get your settings:
Function GetSettings {
$out = #()
Foreach ($objItem in $chklstGetMIPRet.CheckedItems) {
$out += $objItem.ToString()
}
$out
}
$settings_array = GetSettings
Just a simpe tip: you can export in XML with export-clixml and whatever the number of column, they are all added in the file.