Trouble executing powershell script on multiple remote machines - powershell

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.

Related

Combine (add) value from 2 different hashtable together

I was tasked with created a script to pull DHCP stats from a couple of particular scopes. I was able to get the stats no problem, however the output returned 2 results from the same scope. The problem is, whoever created this scope, created 2 identical scopes on 2 different servers but set the unusable address flipped in order to create "round robin"
I.e.
Server 1 has scope 10.10.92.0/23 (255.255.254.0)
10.10.92.48 as start of addresses
10.10.93.249 as end of addresses
10.10.93.1 to 10.10.93.249 IP **EXCLUSION**
Server 2 has scope 10.10.92.0/23 (255.255.254.0)
10.10.92.48 as start of addresses
10.10.93.249 as end of addresses
10.10.92.48 to 10.10.92.255 IP **EXCLUSION**
So my output in the HTML file would show:
1st result
2nd result
I redid my code and I manage to separate the results into hashtables, but now I want to combine the results and then generate a new html file based on that. The below code works but all attempts to try and combine the results resulted in failure or syntax error. Looking for a little guidance here.
$hashtable1 = #{} #create hash table
$scopes1 = Get-DhcpServerv4Scope -ComputerName NAMEOFSERVER | Select-Object ScopeId, SubnetMask, StartRange, EndRange, LeaseDuration, State | Where-Object -FilterScript {$_.ScopeId -like "10.10*" } #get all scopes
foreach ($scope in $scopes1) { $stats1 = Get-DhcpServerv4ScopeStatistics -ComputerName covdhcp5 -ScopeId $scope.scopeid.IPAddressToString | select Free, InUse, Reserved, percentageInUse
$array1 = #()
foreach ($var1 in $stats1) { $array1 += $var1 }
$hashtable1.add($scope.scopeid.IPAddressToString, $array1)}
$hashtable2 = #{} #create hash table
$scopes2 = Get-DhcpServerv4Scope -ComputerName NAMEOFSERVER | Select-Object ScopeId, SubnetMask, StartRange, EndRange, LeaseDuration, State | Where-Object -FilterScript {$_.ScopeId -like "10.10*" } #get all scopes
foreach ($scope in $scopes2) { $stats2 = Get-DhcpServerv4ScopeStatistics -ComputerName covdhcp6 -ScopeId $scope.scopeid.IPAddressToString | select Free, InUse, Reserved, percentageInUse
$array2 = #()
foreach ($var2 in $stats2) { $array2 += $var2 }
$hashtable2.add($scope.scopeid.IPAddressToString, $array2)}

Foreach loops Always runs the last object results

I'm still new with the powershell, there's a questions with my script, my intention is to import the CSV data under column Name
then do a split() for each object with "computername" and "username", however it can catch the arrays with two results, but after the loop in Foreach it only runs the last object. Can someone help Thanks!
whole code
$ImportPath ="C:\data.csv"
$ComputerArray= #()
Import-Csv -Path $ImportPath |ForEach-Object{$ComputerArray+= $_.Name}
Foreach($Hostname in $ComputerArray){
$CharArray =$Hostname.Split("\")
$ComputerName = $CharArray[0]
$Username = $CharArray[1]
}
CSV date looks like
CSV data
$ComputerArray results
----------------------
Computer1\Local User
Computer2\Remote User
Hopes to look like
$ComputerName results
---------------------
Computer1
Computer2
$Username results
---------------------
Local User
Remote User
It's not running the last object only, it's running all the objects but you're only capturing the last one. Both variables $computerName and $userName are getting re-assigned on each iteration.
Below will give you an array of objects with the Computer and User properties for each line of your CSV.
$ImportPath = "C:\data.csv"
$ComputerArray = (Import-Csv -Path $ImportPath).Name
$result = foreach($Hostname in $ComputerArray)
{
$ComputerName, $Username = $Hostname.Split("\")
[pscustomobject]#{
Computer = $ComputerName
User = $Username
}
}
$result | Format-Table
If you want to get the computers on one array and the users on other array like in your expected output you can do $result.Computer and $result.User.

Why is my foreach variable not going to my output in PowerShell after each iteration?

I have DHCP script that looks for matching hostnames in all the scopes on the DHCP servers
I first get all the DHCP servers and import a .txt of hostnames
$list = Get-Content C:\script\HostNameList.txt #Defines content it pulls as list
$DHServers = Get-DhcpServerInDC #gives variable name for loop
# Gets all DHCP servers ands scopes
foreach ($Server in $DHServers){
$scopes = Get-DHCPServerv4Scope -ComputerName $Server.dnsname #get all scopes
}
I loop through list of hostnames and scopes looking for a match. Somewhere in here is my issue
$Output = foreach ($hostname in $list) { #Calls each item in list a hostname and sends to output
if (test-connection -count 1 -computername $hostname -quiet) #With 1 ping, check if hostname is online
{
foreach ($scope in $scopes){
if($scope | Get-DhcpServerV4Lease -ComputerName $server.dnsname | Where-Object HostName -like "$hostName*" ) #compares the hostname to lease to find which scope it is in
{ $scope.name } #return scope it found hostname in
}
[PSCustomObject]#{ #Rename varibles in data pull for output file
Asset = $hostname
Location = $scope.name #only want the name of the scope
Status = "Online"
}
}
else #statement if hostname is not online
{
Write-host "$hostname Is offline, only Last Location is known. $hostname was added to the output file." -BackgroundColor DarkRed
[PSCustomObject]#{
Asset = $hostname
Location = $scope.name #only want the name of the scope, since the name = Location
Status = "Offline"
}
}
}
$Output #show output in powershell
$Output | Export-Csv -Path C:\script\Asset_Result.csv -NoTypeInformation #outputs .csv
This is what it is doing, the output repeats the last item on the list of DHCP scopes.
Asset Location Status
----- -------- ------
A847 Public Internet Online
A261 Public Internet Offline
A201 Public Internet Online
This is what it should be doing
Asset Location Status
----- -------- ------
A847 FLoor 1 Online
A261 West 1st FL Offline
A201 Floor 3 Online
How can I get $scope.name in my
if($scope | ... statement to go to my PSCustomObject after each iteration?
This:
foreach ($Server in $DHServers){
$scopes = Get-DHCPServerv4Scope -ComputerName $Server.dnsname #get all scopes
}
is - in net effect - the equivalent of:
$scopes = Get-DHCPServerv4Scope -ComputerName $DHServers[-1].dnsname #get all scopes
That is, you keep reassigning to the same variable ($scopes) in the loop body, replacing the previous value, so that you end up with only the result from the last loop iteration, for the last server stored in $DHServers, i.e. $DHServers[-1].
The best solution is to rely on PowerShell's ability to use statements such as foreach as an expression whose output - even iterative output from a loop - is automatically collected in an [object[]] array (with two or more outputs) by PowerShell:
# Collect the output from *all* Get-DHCPServerv4Scope calls.
[array] $scopes = foreach ($Server in $DHServers) {
Get-DHCPServerv4Scope -ComputerName $Server.dnsname #get all scopes
}
Note: The [array] type constraint (same as: [object[]]) is only necessary if there can situationally be just one output object and you want to ensure that the collected output is always an array.

Unable to show export-csv in PoweSshell

I have been researching the web to see what am I missing and can't find out, I run the command it goes thru the list of computers but the export doc is always empty.
Here is the code
foreach ($computer in Get-Content "\\NETWORK PATH\user-computers.txt") {
Write-host $computer
$colDrives = Get-WmiObject Win32_MappedLogicalDisk -ComputerName $computer
$Report = #()
# Set our filename based on the execution time
$filenamestring = "$computer-$(get-date -UFormat "%y-%b-%a-%H%M").csv"
foreach ($objDrive in $colDrives) {
# For each mapped drive – build a hash containing information
$hash = #{
ComputerName = $computer
MappedLocation = $objDrive.ProviderName
DriveLetter = $objDrive.DeviceId
}
# Add the hash to a new object
$objDriveInfo = new-object PSObject -Property $hash
# Store our new object within the report array
$Report += $objDriveInfo
}}
# Export our report array to CSV and store as our dynamic file name
$Report | Export-Csv -LiteralPath "\\NETWORK PATH\Drive-Maps.csv" -NoTypeInformation
I want to know what each computer currently got mapped network drives, thanks for all your help and guidance.
I'm not sure why you're not getting output. I've rewritten your script for a few reasons I'd like to point out. First, your variable naming is not very clear. I'm guessing you come from a VBScripting background. Next, you're creating an array and then adding to it - this is simply not needed. You can capture the output of any loop/scriptblock/etc directly by assigning like tihs.
$Report = foreach($thing in $manythings){Do lots of stuff and everything in stdout will be captured}
If you write your script in a way that takes advantage of the pipeline, you can do even more. Next, creating the object with New-Object is slow compared to using the [PSCustomObject] type accelerator introduced in V3. Finally, it seems you create a custom csv for each computer but in the end you just export everything to one file. I'm going to assume you are wanting to collect all this info and put in one CSV.
My recommendation for you to help troubleshoot, run this against your machines and confirm the output on the screen. Whatever you see on the screen should be captured in the report variable. (Except write-host, it's special and just goes to the console)
$computerList = "\\NETWORK PATH\user-computers.txt"
$reportFile = "\\NETWORK PATH\Drive-Maps.csv"
Get-Content $computerList | ForEach-Object {
Write-host $_
$mappedDrives = Get-WmiObject Win32_MappedLogicalDisk -ComputerName $_
foreach ($drive in $mappedDrives)
{
# For each mapped drive – build a hash containing information
[PSCustomObject]#{
ComputerName = $_
MappedLocation = $drive.ProviderName
DriveLetter = $drive.DeviceId
}
}
} -OutVariable Report
Once you know you have all the correct info, run this to export it.
$Report | Export-Csv -LiteralPath $reportFile -NoTypeInformation

Adding objects to array to create multiple columns

I'm trying to get the DisplayName of all Software listed in AddRemovePrograms for each computer, add it to an array under the name of the computer, then export. Here's what I have:
$Computers = gc "C:\Get Software.txt"
$CSV = "C:\Get Software.csv"
$Results = #()
If (Test-Path $CSV) {
Remove-Item $CSV
}
Foreach($Computer in $Computers){
#Get DisplayName of Software Installed on Asset
$Software = Get-WmiObject Win32Reg_AddRemovePrograms -ComputerName $Computer | Select-Object -ExpandProperty DisplayName
$counter = 0
While ($counter -lt $Software.count){
#Create a PSObject. Loops through all software and adds to $Results.
$Obj = New-Object PSOBJECT
Add-member –inputobject $Obj –membertype Noteproperty -Name $Computer -Value $Software[$counter]
$counter++
$Results+=$Obj
}
}
$Results | Export-Csv $CSV -NoTypeInformation
Unfortunately, the output only lists the first Computer in the CSV. I've tried stepping through this to understand it, I just don't understand why I can't add the $Obj variable to $Results with a different Name Property (In this case I'm looping and making a new name based on the computer name in my .txt file). It seems to only take the first input and won't put a new column header then spit out the software. Below is an example of what I'm getting and what I'd like to get instead.
OUTPUT
A01234
Program1
Program2
Program3
EXPECTED OUTPUT
A01234 B05678 C09123
Program1 Program97 Program30
Program2 Program98 Program31
Program3 Program99 Program32
Hopefully this makes some sense. Any assistance would be appreciated, I'm clearly doing something wrong with the objects and I'm not seeing it. Thanks!
This is do-able, it just isn't quite as simple as you would think. What I would suggest is setting things up as a hashtable where the keys are your computer names and your values are an array of software titles for each computer. Then you find out how many titles the computer with the most software has (that's how many rows you'll have, so it's how many times you'll have to loop), and create a loop to make that many objects. Each object will have no properties to start, and then we add a property for each computer in the hashtable, and the value for that property will be the Nth item listed in the hashtable for that computer. Here's the code, it might make this all make more sense:
$Computers = 'A01234','B05678','C09123'
$SftwrPerCmp = [ordered]#{}
ForEach($Computer in $Computers){
$SftwrPerCmp.add($Computer,#())
#Randomly generate 7-15 strings as 'Programs' for current computer
$Software = 1..$(get-random -Maximum 15 -Minimum 7)|%{(Get-Random -inputobject $("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" -split ''|?{$_}) -count 6) -join ''}
ForEach($Title in $Software){
$SftwrPerCmp["$Computer"] += $Title
}
}
$MaxTitleCount = $SftwrPerCmp.Values|%{$_.count}|sort|select -last 1
$Results = #()
For($i=0;$i -lt $MaxTitleCount;$i++){
$Record = New-Object PSObject
$SftwrPerCmp.Keys | ForEach{Add-Member -InputObject $Record -NotePropertyName $_ -NotePropertyValue $SftwrPerCmp["$_"][$i]}
$Results += $Record
Clear-Variable Record
}
$Results|Format-Table -AutoSize
Now obviously you will not randomly generate software titles, you'll use your existing $Software = Get-WmiObject line that's in your existing code instead since that part was at least working for you. This should result in the listings that you were looking for. My code resulted in this:
A01234 B05678 C09123
------ ------ ------
CU7K5E 6GJWOB 97H1TY
7VCZ5T CIPWVK 760NKU
CHKPY0 J4B7D0 1QOSD3
2YEFR4 2VY6DM O68SKU
VI7ZQG WLJQN9 Q5VJAZ
ZQOKNV R9KZG1 H2XZK4
S8IZC4 GRSMPU BIZXKA
LAVNI0 TKBOUC K9DEFU
3U7KVO JZ3X4H
A6GVUK 18AC5H
NMI32Q H14GPJ
50KSZ6 XU0FWC
PAN5TC 9WXR5U
531M04