How could I add an if statement when combining two different arrays - powershell

today I asked
combined two arrays of different size with different properties
Here is the answer
$GroupMembersCount = $null
$GroupMembersCount = gam print groups domain <domain.com> members managers owners countsonly | ConvertFrom-Csv
$GroupSettings = $null
$GroupSettings = gam print groups settings | ConvertFrom-Csv
$GroupMemberCountByEmail = #{}
$GroupMembersCount | ForEach-Object {
$GroupMemberCountByEmail[$_.email] = $_
}
$GroupSettings | Select-Object *,
#{
Name = 'MembersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].MembersCount }
},#{
Name = 'ManagersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].ManagersCount }
},#{
Name = 'OwnersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].OwnersCount }
} |
Export-Csv 'c:\temp\groups.csv' -NoTypeInformation -Encoding UTF8
How can I add a column to this answer above that will give me a True or false if all counts are zero ?
I think I need if before the export above but I am not sure how to If (X -eq 0 -and Y -eq 0) { Do stuff } here since I don't know how to address the X before the export.
| Export-Csv 'c:\temp\groups.csv' -NoTypeInformation -Encoding UTF8

I would just pipe to another Select-Object to keep your code cleaner. It is not required if you want to reuse your hash table lookups in another calculated property.
$GroupSettings | Select-Object *,
#{
Name = 'MembersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].MembersCount }
},#{
Name = 'ManagersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].ManagersCount }
},#{
Name = 'OwnersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].OwnersCount }
} |
Select-Object *,
#{
Name='AllZeroes'
Expression={-not ($_.MembersCount -or $_.ManagersCount -or $_.OwnersCount) }
} |
Export-Csv 'c:\temp\groups.csv' -NoTypeInformation -Encoding UTF8
AllZeroes property will return $true if all counts are 0.

Again I want to add what I did. Please note to any reader some of my answer is different because I use a tool called GAM to help me administrate Google. GAM forced me to pivot from what I accepted above.
PS: I find with most answers here there are adjustments needed. Your mileage may vary so tread carefully.
<#
https://stackoverflow.com/questions/70613438/how-could-i-add-an-if-statement-when-combining-two-different-arrays/70614250#70614250
https://stackoverflow.com/questions/70609905/combined-two-arrays-of-different-size-with-different-properties/70610915?noredirect=1#comment124824384_70610915
#>
$GroupMembersCount = $null
$GroupMembersCount = gam print groups domain <Domain.com> members managers owners countsonly | ConvertFrom-Csv
$GroupSettings = $null
$GroupSettings = gam print groups settings | ConvertFrom-Csv
$GroupsEmpty = $null
$GroupsEmpty = gam config csv_output_row_filter "directMembersCount:count=0" print groups directmemberscount | ConvertFrom-csv
$GroupMemberCountByEmail = #{}
$GroupMembersCount | ForEach-Object {
$GroupMemberCountByEmail[$_.email] = $_
}
$Result = $null
$Result = $GroupSettings | Select-Object *,
#{
Name = 'MembersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].MembersCount }
}, #{
Name = 'ManagersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].ManagersCount }
}, #{
Name = 'OwnersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].OwnersCount }
}
If ($GroupsEmpty) {
$Result | Add-Member -NotePropertyName EmptyGroup -NotePropertyValue $false
function Get-ArrayRowIndex {
param(
[parameter(mandatory = $true)][array]$Property,
[parameter(mandatory = $true)][string]$Value
)
[int]$index = 0
while ($index -lt ($Property.count)) {
if ($Property[$index] -eq $Value) {
return [int]$index
}
$index++
}
return $null
}
$Result | ForEach-Object {
If ($GroupsEmpty.email -contains $_.Email) {
$UpdateRowIndex = $null
#Get-ArrayRowIndex tries to find the item to update in the Result array. $UpdateRowIndex can't be [int]
$UpdateRowIndex = Get-ArrayRowIndex -Property $($Result.email) -Value $_.Email
#update the correct item in the array,changing flag made above.
If ($null -ne $UpdateRowIndex) {
$Result[$UpdateRowIndex].EmptyGroup = $true
}
Else {
#If location in array was not found function Get-ArrayRowIndex.
Write-Error "Problem with final report. Index location in the result array was not found using Get-ArrayRowIndex."
Start-Sleep -Seconds 3
break script
}
}
}
}
$result | Export-Csv 'c:\temp\groups.csv' -NoTypeInformation -Encoding UTF8
Try {
Invoke-Item -Path 'c:\temp\groups.csv'
}
Catch {
write-error "Final report not found at c:\temp\groups.csv"
}
There are better ways to do what I did above but this suits me for now.

Related

Check all lines in a huge CSV file in PowerShell

I want to work with a CSV file of more than 300,000 lines. I need to verify information line by line and then display it in a .txt file in the form of a table to see which file was missing for all servers. For example
Name,Server
File1,Server1
File2,Server1
File3,Server1
File1,Server2
File2,Server2
...
File345,Server76
File346,Server32
I want to display in table form this result which corresponds to the example above:
Name Server1 Server2 ... Server 32 ....Server 76
File1 X X
File2 X X
File3 X
...
File345 X
File346 X
To do this actually, I have a function that creates objects where the members are the Server Name (The number of members object can change) and I use stream reader to split data (I have more than 2 columns in my csv so 0 is for the Server name and 5 for the file name)
$stream = [System.IO.StreamReader]::new($File)
$stream.ReadLine() | Out-Null
while ((-not $stream.EndOfStream)) {
$line = $stream.ReadLine()
$strTempo = $null
$strTempo = $line -split ","
$index = $listOfFile.Name.IndexOf($strTempo[5])
if ($index -ne -1) {
$property = $strTempo[0].Replace("-", "_")
$listOfFile[$index].$property = "X"
}
else {
$obj = CreateEmptyObject ($listOfConfiguration)
$obj.Name = $strTempo[5]
$listOfFile.Add($obj) | Out-Null
}
}
When I export this I have a pretty good result. But the script take so much time (between 20min to 1hour)
I didn't know how optimize actually the script. I'm beginner to PowerShell.
Thanks for the futures tips
You might use HashSets for this:
$Servers = [System.Collections.Generic.HashSet[String]]::New()
$Files = #{}
Import-Csv -Path $Path |ForEach-Object {
$Null = $Servers.Add($_.Server)
if ($Files.Contains($_.Name)) { $Null = $Files[$_.Name].Add($_.Server) }
else { $Files[$_.Name] = [System.Collections.Generic.HashSet[String]]$_.Server }
}
$Table = foreach($Name in $Files.get_Keys()) {
$Properties = [Ordered]#{ Name = $Name }
ForEach ($Server in $Servers) {
$Properties[$Server] = if ($Files[$Name].Contains($Server)) { 'X' }
}
[PSCustomObject]$Properties
}
$Table |Format-Table -Property #{ expression='*' }
Note that in contrast to PowerShell's usual behavior, the .Net HashSet class is case-sensitive by default. To create an case-insensitive HashSet use the following constructor:
[System.Collections.Generic.HashSet[String]]::New([StringComparer]::OrdinalIgnoreCase)
See if this works faster. Change filename as required
$Path = "C:\temp\test1.txt"
$table = Import-Csv -Path $Path
$columnNames = $table | Select-Object -Property Server -Unique| foreach{$_.Server} | Sort-Object
Write-Host "names = " $columnNames
$groups = $table | Group-Object {$_.Name}
$outputTable = [System.Collections.ArrayList]#()
foreach($group in $groups)
{
Write-Host "Group = " $group.Name
$newRow = New-Object -TypeName psobject
$newRow | Add-Member -NotePropertyName Name -NotePropertyValue $group.Name
$servers = $group.Group | Select-Object -Property Server | foreach{$_.Server}
Write-Host "servers = " $servers
foreach($item in $columnNames)
{
if($servers.Contains($item))
{
$newRow | Add-Member -NotePropertyName $item -NotePropertyValue 'X'
}
else
{
#if you comment out next line code doesn't work
$newRow | Add-Member -NotePropertyName $item -NotePropertyValue ''
}
}
$outputTable.Add($newRow) | Out-Null
}
$outputTable | Format-Table

Local users and their groups

I want to get a similar report as in the following thread for a bunch of computers retrieved from the AD.
$adsi = [ADSI]"WinNT://$($WKS.name)"
$adsi.Children | where { $_.SchemaClassName -eq 'user' } | Foreach-Object {
$groups = $_.Groups() | Foreach-Object { $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) }
$_ | Select-Object #{ n = 'UserName'; e = { $_.Name } }, #{ n = 'Groups'; e = { $groups -join ',' } } | | Export-Csv -Path "\\..\KontaLokalne.csv" -NoClobber -Append -Encoding UTF8 -Delimiter ";" -NoTypeInformation
}
I want to get an additional information about each account listed - is the account enabled or not.
Additionally, I would like to send the output to the file in the format: computer name; account name; member of the groups.
Could You help me?
To check whether a user account is enabled or not using ADSI, you will have to test if the ADS_UF_ACCOUNTDISABLE bit in the .UserFlags property is set or not.
To do this, you need to bitwise and the value of the UserFlags with the value of ADS_UF_ACCOUNTDISABLE (2):
$adsi = [ADSI]"WinNT://$($WKS.name)"
$result = $adsi.Children | Where-Object { $_.SchemaClassName -eq 'user' } | ForEach-Object {
$groups = $_.Groups() | Foreach-Object { $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) }
$_ | Select-Object #{Name = 'UserName'; Expression = { $_.Name } },
#{Name = 'Enabled'; Expression = { ($_.UserFlags.Value -band 2) -eq 0} },
#{Name = 'Groups'; Expression = { $groups -join ',' } }
}
# now you can export the resulting collection
# do you really want to use -Append here?
$result | Export-Csv -Path "\\..\KontaLokalne.csv" -NoClobber -Append -Encoding UTF8 -Delimiter ";" -NoTypeInformation
See ADS_USER_FLAG_ENUM

Report confusion. without += Trying to deal with $null

I run an all users command for calendar delegation. I then report. The issue is how do I include someone that has no calendar delegation ? (Code Below)
In this line If ($null -ne $DelegateCal) I make sure someone has calendar delegation then build the object.
If I don't use += I am not sure how to build the object when I add a Else for the $null
<#All Google Calendar delegate report#>
# filename function
Import-Module C:\tasks\Modules\MRNAP\MRNAP.psm1
$AllGoogleUsers = gam print users fields suspended | ConvertFrom-Csv | Where-Object { $_.suspended -eq $False } | Select-Object -ExpandProperty PrimaryEmail
ForEach ($UserEmail in $AllGoogleUsers) {
$DelegateCal = gam calendar $UserEmail print acls | convertfrom-csv | Where-Object { $_.'scope.type' -eq 'user' -and $_.'Scope.value' -ne $UserEmail } -ErrorAction SilentlyContinue
If ($null -ne $DelegateCal) {
$CalendarDelegateList = foreach ($line in $DelegateCal) {
[PSCustomObject]#{
Owner = $line.calendarId
Type = 'Calendar'
Delegate = $line.'scope.value'
Role = $line.role
}
}
}
}
$CalendarDelegateList = $CalendarDelegateList | Sort-Object -Property Owner
$filename = MRNAP -ReportName WhoIsCalendarDelegated -Move
$CalendarDelegateLis | Export-Csv $filename -NoTypeInformation | Format-Table text-align=left -AutoSize
This is how I would do it with +=
$AllGoogleUsers = gam print users fields suspended | ConvertFrom-Csv | Where-Object { $_.suspended -eq $False } | Select-Object -ExpandProperty PrimaryEmail
ForEach ($UserEmail in $AllGoogleUsers) {
$DelegateCal = gam calendar $UserEmail print acls | convertfrom-csv | Where-Object { $_.'scope.type' -eq 'user' -and $_.'Scope.value' -ne $UserEmail } -ErrorAction SilentlyContinue
If ($null -ne $DelegateCal) {
foreach ($line in $DelegateCal) {
$CalendarDelegateList += [PSCustomObject]#{
Owner = $UserEmail
Type = 'Calendar'
Delegate = $line.'scope.value'
Role = $line.role
}
}
}
Else {
$CalendarDelegateList += [PSCustomObject]#{
Owner = $UserEmail
Type = 'Calendar'
Delegate = 'None'
Role = 'None'
}
}
}
It is always preferable to let PowerShell collect statement output in an array for you rather than building a list of outputs manually - both for concision and performance; see this answer for more information.
This even works with nested foreach loops, as in your case.
Applied to your scenario (abridged):
[array] $CalendarDelegateList =
foreach ($UserEmail in $AllGoogleUsers) {
$DelegateCal = gam calendar $UserEmail print acls | ConvertFrom-Csv | Where-Object { $_.'scope.type' -eq 'user' -and $_.'Scope.value' -ne $UserEmail } -ErrorAction SilentlyContinue
If ($null -ne $DelegateCal) {
foreach ($line in $DelegateCal) {
# Construct *and ouput* a [pscustomobject] instance.
[PSCustomObject]#{
Owner = $UserEmail
# ...
}
}
}
Else {
[PSCustomObject]#{
Owner = $UserEmail
# ...
}
}
}
All [pscustomobject] instances (implicitly) output from inside the foreach loop (whether directly or from the nested one) are automatically collected in variable $CalendarDelegateList.
Note:
With two or more output objects from the loop, the $CalendarDelegateList variable receives a regular PowerShell array (of type [object[]]).
The [array] type constraint (short for: [object[]]) additionally ensures that the result is an array even if the loop outputs only one object.

Expanding System.Object[] for Export to CSV

Hello again and sorry!
To keep it really short. What am i doing wrong?
Im attempting to export a list of users filtered by using a customobject to a CSV and it outputs it into the same block. Is there no way to change this? I only ask because, all the other pages ive looked at it keeps telling me to use -join, to join them as strings which does the exact same thing. Is it not possible to output it as multiple rows for each user?
$GPMem = Get-ADGroupMember -Identity security.group | Select-Object -ExpandProperty Name
[array]$TPpl = $GPMem | Where-Object {$_ -like "T*"}
[array]$RPpl = $GPMem | Where-Object {$_ -like "r*"}
[array]$CPpl = $GPMem | Where-Object {$_ -like "c*"}
[pscustomobject]#{
TPeople = (#($TPpl) |Out-String).Trim()
TPCount = $TPpl.Count
RPeople = (#($RPpl) |Out-String).ToString()
RPCount = $TPpl.Count
CPeople = $CPpl
CPCount = $TPpl.Count
} | Export-Csv -Path C:\Users\abraham\Desktop\csv.csv -NoTypeInformation -Force
here is how to insert a ; OR newline into the values of a column ... [grin]
$ThingList = #('One Thing', 'Two Thing', 'Three Thing')
$ThingList -join '; '
'=' * 20
$ThingList -join [System.Environment]::NewLine
output ...
One Thing; Two Thing; Three Thing
====================
One Thing
Two Thing
Three Thing
create 3 more arrays for the count (each array will be exported to a column), then find the array with the most count and generate a psobject for each line.
[array]$TPpl = #("T1" ,"T2", "T3")
[array]$TPpl_count = #($TPpl.Count)
[array]$RPpl = #("R1" ,"R2", "R3", "R4")
[array]$RPpl_count = #($RPpl.Count)
[array]$CPpl = #("C1" ,"C2", "C3", "C4","C5")
[array]$CPpl_count = #($CPpl.Count)
$leng = [array]$TPpl.Count,$RPpl.Count,$CPpl.Count
$max = ($leng | measure -Maximum).Maximum
$csv = for($i=0;$i -lt $max;$i++){
New-Object -TypeName psobject -Property #{
"TPeople" = $(if ($TPpl[$i]) { $TPpl[$i]})
"TPCount" = $(if ($TPpl_count[$i]) { $TPpl_count[$i]})
"RPeople" = $(if ($RPpl[$i]) { $RPpl[$i]})
"RPCount" = $(if ($RPpl_count[$i]) { $RPpl_count[$i]})
"CPeople" = $(if ($CPpl[$i]) { $CPpl[$i]})
"CPCount" = $(if ($CPpl_count[$i]) { $CPpl_count[$i]})
}
}
$csv | Export-Csv C:\Temp\test.csv -NoTypeInformation
result:
your final code should be:
$GPMem = Get-ADGroupMember -Identity security.group | Select-Object -ExpandProperty Name
[array]$TPpl = $GPMem | Where-Object {$_ -like "T*"}
[array]$RPpl = $GPMem | Where-Object {$_ -like "r*"}
[array]$CPpl = $GPMem | Where-Object {$_ -like "c*"}
[array]$TPpl_count = #($TPpl.Count)
[array]$RPpl_count = #($RPpl.Count)
[array]$CPpl_count = #($CPpl.Count)
$leng = [array]$TPpl.Count,$RPpl.Count,$CPpl.Count
$max = ($leng | measure -Maximum).Maximum
$csv = for($i=0;$i -lt $max;$i++){
New-Object -TypeName psobject -Property #{
"TPeople" = $(if ($TPpl[$i]) { $TPpl[$i]})
"TPCount" = $(if ($TPpl_count[$i]) { $TPpl_count[$i]})
"RPeople" = $(if ($RPpl[$i]) { $RPpl[$i]})
"RPCount" = $(if ($RPpl_count[$i]) { $RPpl_count[$i]})
"CPeople" = $(if ($CPpl[$i]) { $CPpl[$i]})
"CPCount" = $(if ($CPpl_count[$i]) { $CPpl_count[$i]})
}
}
$csv | Export-Csv C:\Temp\test.csv -NoTypeInformation

How to add 2 columns in my PowerShell HTML report?

I have PowerShell script, I wrote this code:
$computers = Get-Content D:\Dev\Powershell\Powershell_TXT_FILE\pickup1.txt | Where {
-not ($_.StartsWith('#'))
} | foreach {
if (Test-Connection $_ -Quiet -Count 1) {
New-Object psobject -Property #{
Server = $_
Status = "Online"
}
} else {
New-Object PSObject -Property #{
Server = $_
Status = "Offline"
}
}
}
$computers | ConvertTo-Html -Property Server | Foreach {
if ($_ -like "*<td>Online</td>*" ) {
$_ -replace "<tr>","<tr bgcolor=green>"
} else {
$_ -replace "<tr>","<tr bgcolor=red>"
}
} | Out-File D:\Share\Powershell\Powershell_TXT_FILE\test.html
How to add 2 or 4 columns on script.
I want to get this conclusion:
Are you wanting columns 3 & 4 to exist but always be blank?
If so, try:
$computers = Get-Content D:\Dev\Powershell\Powershell_TXT_FILE\pickup1.txt | Where {
-not ($_.StartsWith('#'))
} | foreach {
if (Test-Connection $_ -Quiet -Count 1) {
New-Object psobject -Property #{
Server = $_
Status = "Online"
}
} else {
New-Object PSObject -Property #{
Server = $_
Status = "Offline"
}
}
}
$computers | ConvertTo-Html -Property Server | Foreach {
if ($_ -like "*<td>Online</td>*" ) {
$_ -replace "<tr>","<tr bgcolor=green>"
} else {
$_ -replace "<tr>","<tr bgcolor=red>"
} | %{$_ -replace "</td></tr>","</td><td> </td><td> </td></tr>"}
} | Out-File D:\Share\Powershell\Powershell_TXT_FILE\test.html
This adds the two columns with a space in them, to the end of each row in your table. (based on the value in the HTML table.
Now, if you don't mind headers, it easier, add to the first line the data fields you want..
$computers = Get-Content D:\Dev\Powershell\Powershell_TXT_FILE\pickup1.txt | Where { -not ($_.StartsWith('#')) | select *,xx,yy
where XX and YY are your two added fields in your HTML table.