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

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.

Related

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

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.

Multiple outputs to same csv file in Powershell

I'm trying to export my firewall rules that are specified in multiple group policy objects and would like to include the Name of the GPO in the exported file. So I tried to take my string variable $Policy and jam it into the csv file each time a new gpo is parsed but all I'm getting is the gpo name and not the fields from Get-NetFirewallRule. Of course if I remove the $policy | Out-File $env:temp\gpos.csv -Append -Force line then I get all of the fields from Get-NetFirewallRule - but they're all on a large csv file and I can't determine their source GPO.
foreach ($policy in $PolicyObjects) {
$GPO = Open-NetGPO -PolicyStore "contoso.com\$policy"
$policy | Out-File $env:temp\gpos.csv -Append -Force
Get-NetFirewallRule -GPOSession $GPO |
Select Name,
DisplayName,
DisplayGroup,
#{Name='Protocol';Expression={($PSItem | Get-NetFirewallPortFilter).Protocol}},
#{Name='LocalPort';Expression={($PSItem | Get-NetFirewallPortFilter).LocalPort}},
#{Name='RemotePort';Expression={($PSItem | Get-NetFirewallPortFilter).RemotePort}},
#{Name='RemoteAddress';Expression={($PSItem | Get-NetFirewallAddressFilter).RemoteAddress}},
Enabled,
Profile,
Direction,
Action | Export-CSV $env:temp\gpos.csv -Append -Force
}
Start-Process notepad $env:temp\gpos.csv
Seems your $PolicyObjects is a list of your group policy displaynames. I'd tighten up your code in one of the following manners.
$PolicyObjects | ForEach-Object {
$GPO = Open-NetGPO -PolicyStore "contoso.com\$_"
foreach($rule in Get-NetFirewallRule -GPOSession $GPO)
{
$portfilter = $rule | Get-NetFirewallPortFilter
$addressfilter = $rule | Get-NetFirewallAddressFilter
[PSCustomObject]#{
GPOName = $_
RuleName = $rule.name
DisplayName = $rule.displayname
DisplayGroup = $rule.displaygroup
Protocol = $portfilter.Protocol
LocalPort = $portfilter.LocalPort
RemotePort = $portfilter.RemotePort
RemoteAddress = $addressfilter.RemoteAddress
Enabled = $rule.enabled
Profile = $rule.profile
Direction = $rule.direction
Action = $rule.action
}
}
} | Export-CSV $env:temp\gpos.csv -Force
Start-Process notepad $env:temp\gpos.csv
or
$csvdata = foreach($policy in $PolicyObjects)
{
$GPO = Open-NetGPO -PolicyStore "contoso.com\$policy"
foreach($rule in Get-NetFirewallRule -GPOSession $GPO)
{
$portfilter = $rule | Get-NetFirewallPortFilter
$addressfilter = $rule | Get-NetFirewallAddressFilter
[PSCustomObject]#{
GPOName = $policy
RuleName = $rule.name
DisplayName = $rule.displayname
DisplayGroup = $rule.displaygroup
Protocol = $portfilter.Protocol
LocalPort = $portfilter.LocalPort
RemotePort = $portfilter.RemotePort
RemoteAddress = $addressfilter.RemoteAddress
Enabled = $rule.enabled
Profile = $rule.profile
Direction = $rule.direction
Action = $rule.action
}
}
}
$csvdata | Export-CSV $env:temp\gpos.csv -Force
Start-Process notepad $env:temp\gpos.csv
In the first one we change the outer loop to a Foreach-Object to take advantage of the pipeline and piping straight to Export-Csv.
In the second we capture all the output then export.
In both we limit the execution time by limiting the opening/writing to file to one time, limit the portfilter calls to one per rule instead of 3, and we use the [PSCustomObject] type accelerator to construct our final object instead of piping to Select-Object with calculated expressions. Both should achieve your desired result if I understood correctly.
Does this make a difference?
$csv=foreach ($policy in $PolicyObjects) {
$GPO = Open-NetGPO -PolicyStore "contoso.com\$policy"
$policy | Out-File $env:temp\gpos.csv -Append -Force
Get-NetFirewallRule -GPOSession $GPO |
Select Name,
DisplayName,
DisplayGroup,
#{Name='Protocol';Expression={($PSItem | Get-NetFirewallPortFilter).Protocol}},
#{Name='LocalPort';Expression={($PSItem | Get-NetFirewallPortFilter).LocalPort}},
#{Name='RemotePort';Expression={($PSItem | Get-NetFirewallPortFilter).RemotePort}},
#{Name='RemoteAddress';Expression={($PSItem | Get-NetFirewallAddressFilter).RemoteAddress}},
Enabled,
Profile,
Direction,
Action
}
$csv | Export-CSV $env:temp\gpos.csv -Append -Force
Start-Process notepad $env:temp\gpos.csv
It's not possible to append data with Export-Csv when the target file does not match the structure.
In your case, Export-Csv tried to append values from column $policy, which doesn't exist. Therefore it added $null.
For a valid CSV file you have to combine everything into one object before using Export-Csv.
Try the below approach, which should work much faster, since it does not use Get-NetFirewallAddressFilter several times for the same item, but only once for per GPO-Session.
Unfortunately I can't test it with a GPO-Session, but since the Parameter -GPOSession exists for Get-NetFirewallPortFilter and Get-NetFirewallAddressFilter as well, it may work.
If you want to test it just locally, you just need to remove the outer foreach-loop and all GPOSession related commands and parameters. Open-NetGPO and -GPOSession
Comparison between my approach and retrieving the Port- and Address-Filter for each rule separately (even when only once per rule) when building a report for local firewall rules:
My approach: < 2 seconds
Other : > 3 minutes (using Get-NetFirwall*Filter for each rule once)
The code requires an elevated console!
#requires -RunAsAdministrator
$ruleReport = [System.Collections.Generic.List[psobject]]::new()
foreach ($policy in $PolicyObjects) {
<#
if you want to append to file after each policy, place the list here instead of above
$ruleReport = [System.Collections.Generic.List[psobject]]::new()
#>
$GPO = Open-NetGPO -PolicyStore "contoso.com\$policy"
$rules = Get-NetFirewallRule -GPOSession $GPO
# retrieving filters once in advance speeds up the execution extremely! (Requires elevated session to get all filters)
# local execution time with that approach reduces the execution time from >3 minutes TO <2 seconds
$portFilters = Get-NetFirewallPortFilter -All -GPOSession $GPO
$addressFilters = Get-NetFirewallAddressFilter -All -GPOSession $GPO
# build dictionary for port filters for rule lookups
$portFilterDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($portFilter in $portFilters) {
$portFilterDict.Add($portFilter.InstanceID, $portFilter)
}
# build dictionary for addresss filters for rule lookups
$addressFilterDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($addressFilter in $addressFilters) {
$addressFilterDict.Add($addressFilter.InstanceID, $addressFilter)
}
foreach ($rule in $rules) {
$ruleInstanceId = $rule.InstanceID
$rulePortFilter = $portFilterDict[$ruleInstanceId]
$ruleAddressFilter = $addressFilterDict[$ruleInstanceId]
# build combined object
$ruleReportItem = [pscustomobject][ordered]#{
Policy = $policy
Name = $rule.Name
DisplayName = $rule.DisplayName
DisplayGroup = $rule.DisplayGroup
Protocol = $rulePortFilter.Protocol
LocalPort = $rulePortFilter.LocalPort
RemotePort = $rulePortFilter.RemotePort
RemoteAddress = $ruleAddressFilter.RemoteAddress
Enabled = $rule.Enabled
Profile = $rule.Profiles
Direction = $rule.Direction
Action = $rule.Action
}
# append to list
$ruleReport.Add($ruleReportItem)
}
<#
if you want to append to file after each policy, place the export here instead of below
$ruleReport | Export-Csv $env:temp\gpos.csv -Append
#>
}
$ruleReport | Export-Csv $env:temp\gpos.csv
Local Firewall Rules
To test the approach just for local Firewall Rules, use this script.
This script adds all available information of each rule and requires an elevated console.
#requires -RunAsAdministrator
$ruleReport = [System.Collections.Generic.List[psobject]]::new()
$rules = Get-NetFirewallRule
$portFilters = Get-NetFirewallPortFilter -All
$addressFilters = Get-NetFirewallAddressFilter -All
$applicationFilters = Get-NetFirewallApplicationFilter -All
$interfaceFilters = Get-NetFirewallInterfaceFilter -All
$interfaceTypeFilters = Get-NetFirewallInterfaceTypeFilter -All
$securityFilters = Get-NetFirewallSecurityFilter -All
$serviceFilters = Get-NetFirewallServiceFilter -All
$portFilterDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($filter in $portFilters) {
$portFilterDict.Add($filter.InstanceID, $filter)
}
$addressFilterDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($filter in $addressFilters) {
$addressFilterDict.Add($filter.InstanceID, $filter)
}
$applicationFiltersDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($filter in $applicationFilters) {
$applicationFiltersDict.Add($filter.InstanceID, $filter)
}
$interfaceFiltersDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($filter in $interfaceFilters) {
$interfaceFiltersDict.Add($filter.InstanceID, $filter)
}
$interfaceTypeFiltersDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($filter in $interfaceTypeFilters) {
$interfaceTypeFiltersDict.Add($filter.InstanceID, $filter)
}
$securityFiltersDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($filter in $securityFilters) {
$securityFiltersDict.Add($filter.InstanceID, $filter)
}
$serviceFiltersDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($filter in $serviceFilters) {
$serviceFiltersDict.Add($filter.InstanceID, $filter)
}
$cimClassProperties = 'CimClass', 'CimInstanceProperties', 'CimSystemProperties'
$cimClassPropertiesLookup = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
foreach ($cimClassProperty in $cimClassProperties) {
[void] $cimClassPropertiesLookup.Add($cimClassProperty)
}
foreach ($rule in $rules) {
$ruleInstanceId = $rule.InstanceID
$portFilter = $portFilterDict[$ruleInstanceId]
$addressFilter = $addressFilterDict[$ruleInstanceId]
$applicationFilter = $applicationFiltersDict[$ruleInstanceId]
$interfaceFilter = $interfaceFiltersDict[$ruleInstanceId]
$interfaceTypeFilter = $interfaceTypeFiltersDict[$ruleInstanceId]
$securityFilter = $securityFiltersDict[$ruleInstanceId]
$serviceFilter = $serviceFiltersDict[$ruleInstanceId]
$ruleReportItemProps = [ordered]#{
RuleDisplayName = $rule.DisplayName
RuleDescription = $rule.Description
RuleEnabled = $rule.Enabled
RuleProfile = $rule.Profile
RuleAction = $rule.Action
RuleDirection = $rule.Direction
PortFilterProtocol = $rulePortFilter.Protocol
PortFilterLocalPort = $rulePortFilter.LocalPort
PortFilterRemotePort = $rulePortFilter.RemotePort
AddressFilterRemoteAddress = $ruleAddressFilter.RemoteAddress
ApplicationFilterProgram = $applicationFilter.Program
}
foreach ($prop in $rule.psobject.Properties) {
$propName = 'Rule' + $prop.Name
if (-not $ruleReportItemProps.Contains($propName ) -and -not $cimClassPropertiesLookup.Contains($prop.Name)) {
$ruleReportItemProps.Add($propName , $prop.Value)
}
}
foreach ($prop in $portFilter.psobject.Properties) {
$propName = 'PortFilter' + $prop.Name
if (-not $ruleReportItemProps.Contains($propName) -and -not $cimClassPropertiesLookup.Contains($prop.Name)) {
$ruleReportItemProps.Add($propName, $prop.Value)
}
}
foreach ($prop in $addressFilter.psobject.Properties) {
$propName = 'AddressFilter' + $prop.Name
if (-not $ruleReportItemProps.Contains($propName) -and -not $cimClassPropertiesLookup.Contains($prop.Name)) {
$ruleReportItemProps.Add($propName, $prop.Value)
}
}
foreach ($prop in $applicationFilter.psobject.Properties) {
$propName = 'ApplicationFilter' + $prop.Name
if (-not $ruleReportItemProps.Contains($propName) -and -not $cimClassPropertiesLookup.Contains($prop.Name)) {
$ruleReportItemProps.Add($propName, $prop.Value)
}
}
foreach ($prop in $interfaceFilter.psobject.Properties) {
$propName = 'InterfaceFilter' + $prop.Name
if (-not $ruleReportItemProps.Contains($propName) -and -not $cimClassPropertiesLookup.Contains($prop.Name)) {
$ruleReportItemProps.Add($propName, $prop.Value)
}
}
foreach ($prop in $interfaceTypeFilter.psobject.Properties) {
$propName = 'InterfaceTypeFilter' + $prop.Name
if (-not $ruleReportItemProps.Contains($propName) -and -not $cimClassPropertiesLookup.Contains($prop.Name)) {
$ruleReportItemProps.Add($propName, $prop.Value)
}
}
foreach ($prop in $securityFilter.psobject.Properties) {
$propName = 'SecurityFilter' + $prop.Name
if (-not $ruleReportItemProps.Contains($propName) -and -not $cimClassPropertiesLookup.Contains($prop.Name)) {
$ruleReportItemProps.Add($propName, $prop.Value)
}
}
foreach ($prop in $serviceFilter.psobject.Properties) {
$propName = 'ServiceFilter' + $prop.Name
if (-not $ruleReportItemProps.Contains($propName) -and -not $cimClassPropertiesLookup.Contains($prop.Name)) {
$ruleReportItemProps.Add($propName, $prop.Value)
}
}
$ruleReport.Add([pscustomobject]$ruleReportItemProps)
}
$ruleReport | Sort-Object RuleEnabled, RuleDisplayName, RuleProfile | ogv

Pulling last log on time in ps script showing blank field

I have been tasked with pulling a list of all MFA enabled/disabled accounts in our environment. I found a script and modified with with extra fields I needed, one of them being Last Log On Time. I need this basically for to filter through the report to remove any type of service accounts/ external account ect. The code I have just outputs a blank field when exporting to the csv.
I've tried every variation of LastLogon I could find (time,date,ect)
$Report = #()
$i = 0
$Accounts = (Get-MsolUser -All | ? {$_.StrongAuthenticationMethods -ne $Null} | Sort DisplayName)
ForEach ($Account in $Accounts) {
Write-Host "Processing" $Account.DisplayName
$i++
$Methods = $Account | Select -ExpandProperty StrongAuthenticationMethods
$MFA = $Account | Select -ExpandProperty StrongAuthenticationUserDetails
$State = $Account | Select -ExpandProperty StrongAuthenticationRequirements
$Methods | ForEach { If ($_.IsDefault -eq $True) {$Method = $_.MethodType}}
If ($State.State -ne $Null) {$MFAStatus = $State.State}
Else {$MFAStatus = "Disabled"}
$ReportLine = [PSCustomObject][Ordered]#{
User = $Account.DisplayName
UPN = $Account.UserPrincipalName
Department = $Account.Department
Office = $Account.Office
LastLogon = $Account.LastLogon
MFAMethod = $Method
MFAPhone = $MFA.PhoneNumber
MFAEmail = $MFA.Email
MFAStatus = $MFAStatus }
$Report += $ReportLine }
Write-Host $i "accounts are MFA-enabled"
$Report | Export-CSV -NoTypeInformation c:\temp\MFAUsers.CSV
Any help with exporting the last log on date would be great.
I cannot test this, but I think you need LastLogon = (Get-MailboxStatistics -Identity $Account.UserPrincipalName).LastLogonTime.
As a sidenote, adding objects to an array is slow and there is an easier method of collecting the emitted objects into the $Report variable:
$mfaEnabled = 0
$Accounts = (Get-MsolUser -All | ? {$_.StrongAuthenticationMethods -ne $Null} | Sort DisplayName)
$Report = foreach ($Account in $Accounts) {
Write-Host "Processing" $Account.DisplayName
$Methods = $Account | Select -ExpandProperty StrongAuthenticationMethods
$MFA = $Account | Select -ExpandProperty StrongAuthenticationUserDetails
$State = $Account | Select -ExpandProperty StrongAuthenticationRequirements
$Methods | ForEach-Object { If ($_.IsDefault -eq $True) {$Method = $_.MethodType}}
If ($State.State) {$MFAStatus = $State.State} else {$MFAStatus = "Disabled"}
# update the counter for MFA Enabled users
if ($MFAStatus -eq 'Enabled') { $mfaEnabled++ }
# just emit the PSObject here, it will be collected in the $Report variable.
# also, no need to use [ordered] on [PSCustomObject]
[PSCustomObject]#{
User = $Account.DisplayName
UPN = $Account.UserPrincipalName
Department = $Account.Department
Office = $Account.Office
LastLogon = (Get-MailboxStatistics -Identity $Account.UserPrincipalName).LastLogonTime
MFAMethod = $Method
MFAPhone = $MFA.PhoneNumber
MFAEmail = $MFA.Email
MFAStatus = $MFAStatus
}
}
Write-Host "$mfaEnabled accounts are MFA-enabled"
$Report | Export-CSV -NoTypeInformation c:\temp\MFAUsers.CSV

If clause in Generic List

I have some questions about GenericLists in PowerShell.
The script below prints all access rights on a file share for a specific user with his groups. Now I want to add a new row in my GenericList which shows from where (user/group) the right is inherited.
$User = "Testumgebung\cbruehwiler"
$UserOhneDomain = "cbruehwiler"
$Path = "T:\"
$List = New-Object System.Collections.Generic.List[System.Object]
$Groups = Get-ADPrincipalGroupMembership $UserOhneDomain
$GroupArrayList = New-Object System.Collections.ArrayList
foreach ($Group in $Groups) {
$GroupArrayList.Add($Group.Name) | Out-Null
}
# Fields we want in list, an array of calculated properties.
$OutputFields = #(
#{name="Item" ; expression={$_.Path.split(':',3)[-1]}}
#{name="Rights" ; expression={$Right.FileSystemRights}}
#{name="AccessType" ; expression={$Right.AccessControlType}}
#{name="From" ; expression={$User}}
)
$FileSystemObjects = Get-ChildItem $Path -Recurse | ForEach-Object {Get-Acl $_.FullName}
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
if ($Right.IdentityReference -eq $User) {
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
foreach ($GroupArrayItem in $GroupArrayList){
if ($Right.IdentityReference -eq ("TESTUMGEBUNG\" + $GroupArrayItem)) {
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
}
$List | Out-File C:\Users\cbruehwiler\Desktop\PermissionCheck.txt
The result looks like this:
Item Rights AccessType From
---- ------ ---------- ----
T:\TestFolder FullControl Allow Testumgebung\cbruehwiler
T:\TestFolder Read, Synchronize Allow Testumgebung\cbruehwiler
T:\TestFolder Write, ReadAndExecute, Synchronize Allow Testumgebung\cbruehwiler
The last row now prints only my user. However it should show the user or the group.
You could even merge the two loops into one, like this:
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
foreach ($GroupArrayItem in $GroupArrayList) {
# not needed; just for convenience
[string]$id = $Right.IdentityReference
# test if the $Right.IdentityReference corresponds with the user name
if ($id -eq $User) {
$List.Add(($Item | Select-Object $OutputFields))
}
# test if the $Right.IdentityReference without the 'Domain\' part can be found in the list of groups
elseif (($id.Split("\", 2)[-1]) -in $GroupArrayList) {
# set the $User variable to the value of $Right.IdentityReference
$User = "Group: $id"
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
}

PowerShell get permissions on a folder for a user with group

I have to print the permissions of a user for a specific folder. Below, I have a working code. However it only scans if the user has been specifically given permissions. Now I also want to check, if a group of which the user is a member of, has permissions on the folder.
I thought about listing all groups in which my user is a MemberOf and then add them into a Generic List. Afterwards, I will execute the following code for each entry of this list.
$User = "testumgebung\cbruehwiler"
$Path = "T:\"
# Generic list object to store output in
$List = New-Object System.Collections.Generic.List[System.Object]
# Fields we want in list, an array of calculated properties.
$OutputFields = #(
#{name="Item" ; expression={$_.Path.split(':',3)[-1]}}
#{name="Rights" ; expression={$Right.FileSystemRights}}
#{name="AccessType" ; expression={$Right.AccessControlType}}
# #{name="User" ; expression={$User}}
)
# Store all objects in variable
$FileSystemObjects = Get-ChildItem $Path -Recurse | ForEach-Object {Get-Acl $_.FullName}
# Iterate through every object
foreach ($Item in $FileSystemObjects) {
# Iterate through every individual user right within each object
# Add it to our list if it matchers our $User
foreach ($Right in $Item.Access) {
if ($Right.IdentityReference -eq $User) {
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
$List | Out-File C:\Users\cbruehwiler\Desktop\PermissionCheck.txt
My list prints the folder name, the different permissions and if it has access or not. I don't really want to change the structure too much.
I found a solution.
Import-Module ActiveDirectory
$User = "Testumgebung\cbruehwiler"
$UserOhneDomain = "cbruehwiler"
$Path = "T:\"
$List = New-Object System.Collections.Generic.List[System.Object]
$Groups = Get-ADPrincipalGroupMembership $UserOhneDomain
$GroupArrayList = New-Object System.Collections.ArrayList
foreach ($Group in $Groups)
{
$GroupArrayList.Add($Group.Name) | Out-Null
}
# Fields we want in list, an array of calculated properties.
$OutputFields = #(
#{name="Item" ; expression={$_.Path.split(':',3)[-1]}}
#{name="Rights" ; expression={$Right.FileSystemRights}}
#{name="AccessType" ; expression={$Right.AccessControlType}}
# #{name="User" ; expression={$User}}
)
$FileSystemObjects = Get-ChildItem $Path -Recurse | ForEach-Object {Get-Acl $_.FullName}
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
if ($Right.IdentityReference -eq $User)
{
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
foreach ($GroupArrayItem in $GroupArrayList){
if ($Right.IdentityReference -eq ("TESTUMGEBUNG\" + $GroupArrayItem))
{
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
}
$List | Out-File C:\Users\cbruehwiler\Desktop\PermissionCheck.txt
This Script checks all rights from a user on a file or share including all of the groups the user is a member of.
You just have to enter the user once with the "DOMAIN\username" variant and once with the "username" variant.
Hope this helps