Foreach loops Always runs the last object results - powershell

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.

Related

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

How do you export a list of PC names and a specific group membership of that endpoint using powershell?

I have a list of end points and we are trying to see if they have this specific group membership but I cannot figure out how to export the endpoint and the group its a member of.
$Groups = foreach ($pc in (Get-Content "C:\Users\*\Desktop\DefualtTest.csv")) {
try {
Get-ADComputer $pc -Properties memberof |
select -Expand memberof |
dsget group -samid |
? {$_ -match 'bit9'}
} catch {
Write-Output "$pc does not have bit9 group"
}
}
$Groups | Out-File "C:\Users\*\Desktop\testONE.csv"
trying to do this in comments is ... to difficult. [grin]
here's an example of what i mean by "do it one line at a time" in the foreach loop ...
# fake reading a list of systems from a text file
# in real life, use Get-Content
$ComputerList = #'
pc01
pc02
pc03
pc04
pc05
pc666
'# -split [environment]::NewLine
# fake getting a list of Computers & the groups they are in
# in real life, use Get-ADComputer & the ".MemberOf" property
$GroupMembershipLookup = #{
'pc01' = #('GroupA', 'GroupB')
'pc02' = #('GroupB', 'GroupC', 'GroupD')
'pc03' = #('GroupA', 'GroupB', 'GroupE')
'pc04' = #('GroupZ')
}
$NoGroupFound = '__No Group Found__'
$Results = foreach ($CL_Item in $ComputerList)
{
# use a real call to Get-ADComputer here [*grin*]
# pro'ly something like "(Get-ADComputer $CL_Item -Property MemberOf).MemberOf -join '; '"
$GroupList = $GroupMembershipLookup[$CL_Item] -join '; '
if ([string]::IsNullOrEmpty($GroupList))
{
$GroupList = $NoGroupFound
}
[PSCustomObject]#{
ComputerName = $CL_Item
GroupMembership = $GroupList
}
}
# on screen
$Results
# to CSV
$Results |
Export-Csv -LiteralPath "$env:TEMP\Del_GroupMembershipList.csv" -NoTypeInformation
on screen output ...
ComputerName GroupMembership
------------ ---------------
pc01 GroupA; GroupB
pc02 GroupB; GroupC; GroupD
pc03 GroupA; GroupB; GroupE
pc04 GroupZ
pc05 __No Group Found__
pc666 __No Group Found__
csv content ...
"ComputerName","GroupMembership"
"pc01","GroupA; GroupB"
"pc02","GroupB; GroupC; GroupD"
"pc03","GroupA; GroupB; GroupE"
"pc04","GroupZ"
"pc05","__No Group Found__"
"pc666","__No Group Found__"
what the above does ...
creates an array of system names as if they had been read in via Get-Content
creates a lookup table as if one had used Get-ADComputer to get the group membership for each system
made a "not found" value & stored that in a $Var for reuse
iterated thru the $ComputerList collection
grabbed the list/array of groups and converted them into a semicolon delimited string
tested for "no groups found" & added the value saved earlier if the list was empty
built a [PSCustoObject] and sent that to the output stream
captured the output stream to an array named $Results all at one time to avoid the += array penalty
displayed the results on screen
sent the $Results collection to a CSV file

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 Nested Hashtables - Export to file

Im pulling out information from AD to output alot of information to be handled at a later point.
I need to export this to a csv or something so i can have a rollback ( in effect import file so i can handle each object like i can with the hashtable below. Especially that inside $_.MemberOf).
The hashtable im struggling with handling is:
$logging1 = #{
MemberOf="$users.MemberOf"
OriginalOU="$Ou.DistinguishedName"
DisabledWhen="$descriptionDisabled"
}
$logging = #{$users.SamAccountname = $logging1}
what the rest of the script does is(that isnt pasted ) is:
iterate through a bunch of ou's. Getting users last logged in -90 days ago or more then passing them to the HashTable in question which i like to append to a file to be imported at a later time.
I have been googling for hours without getting somewhere
here is the complete script:
import-module ActiveDirectory
$descriptionDisabled = get-date -Format yyyyMMdd
$Loggdir = "C:\temp"
$array = #{}
$loggname = get-date -f yyyyMMdd
$90days = ((get-date).AddDays(-90))
$searchBase = 'OU=someou4,OU=someou3,OU=someou2,OU=someou1,DC=name,DC=NO'
$ExclusionList = Someexlutions
$OUlist = Get-ADOrganizationalUnit -SearchBase $searchBase
foreach ($Ou in $OUlist)
{
$ExpiredADusers = get-aduser -Filter {(LastlogonDate -le $90days)
-and (Enabled -eq $True)}
-SearchBase $ou.distinguishedname
-Properties *
#Get information about every user and their groupmembership
foreach ($Users in $ExpiredADusers)
{
$users = get-aduser -Identity $users.SamAccountName
-Properties *
| Select-Object -Property SamAccountName, MemberOf,
DistinguishedName, ObjectGUID
$logging = #{$person.SamAccountName = #{
MemberOf=$person.MemberOf;
OriginalOU=$Ou.DistinguishedName;
DisabledWhen="$descriptionDisabled"}}
$Array += $Logging
}
}
$array | export-clixml -path somepath -noclobber
EDIT SOLVED
Solved problem and original script in question has been updated to handle Nested hashtables
Original problem
I think you can understand what im trying to do.
Im making a rollback file incase needed.
So the exported file need to be easy to import. when using hashtables you can use . notations and each objects under memberof is treated as an object
What the rollback needs is so i can iterate through the imported info to move the user back to its original OU placement and restore membership
I think EBGreen really hit the nail on the head here. You need an array of custom objects, and then you can just export it to a CSV like you want to. It's a really minor change in code too.
$Array = #()
$ForEach($User in $Users){
$logging1 = New-Object PSObject -Property #{
MemberOf=$user.MemberOf
OriginalOU=$Ou.DistinguishedName
DisabledWhen=$descriptionDisabled
}
$Array += $Logging1
}
Edit: Hm, so you want to be able to export and import full objects. You don't want a CSV then because you have nested arrays, and a CSV is not designed to handle that for export and import. You need XML, so, as mentioned above, you need to use Export-Clixml and Import-Clixml since XML can handle nested arrays. Just pipe the array to it once the array has all your data and you should be all set.
Edit2: The hashtables within hashtables issue... Ok, so we had it as MemberOf="$user.MemberOf" and that's the issue. It is converting it to a string, so it is expanding the entire $User variable, and tacking .MemberOf to the end of it. We don't really want to do it in this case, but if you want to access a property of an object from within doublequotes you need to put $() around it. For example if you wanted to include the user's distinguishedname as a part of human friendly output you could do something like:
Write-Output "$($Users.Name)'s distinguished name is: $($users.distinguishedname)"
Which would output something like:
TMTech's distinguished name is: CN=TMTech,OU=Awesome,OU=Administrators,DC=Digital,DC=Ghost,DC=net

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.