Powershell foreach results to array - powershell

I'm trying to run the below code and get the results to an HTML email. If I just run it as is, all of the output is on one line, which is a mess. I thought I could save the foreach right to an array, but that's not working. . . Just getting a blank array. If I run it without, I get output to the screen, so I know I'm getting results.
Connect-MsolService -Credential $Credential
$customers = Get-MsolPartnerContract -All
$body = #(foreach ($customer in $customers) {
if ($customer.defaultdomainname -notlike "*Domain1*" -and $customer.defaultdomainname -notlike "*DomainABC*") {
Get-MsolUser -All -EnabledFilter EnabledOnly -TenantId $($customer.TenantId) | ? {$_.StrongAuthenticationRequirements.State -eq $null -and $_.isLicensed -eq "TRUE"} | select UserPrincipalName
}
})

I don't have an instance to play/validate with, but try it this way (use whatever code or other formatting you choose of course):
Connect-MsolService -Credential $Credential
[System.Collections.ArrayList]$body = #()
(Get-MsolPartnerContract -All) |
ForEach-Object {
if ($PSItem.defaultdomainname -notlike '*Domain1*' -and
$PSItem.defaultdomainname -notlike '*DomainABC*'
)
{
$body.Add(
(
Get-MsolUser -All -EnabledFilter EnabledOnly -TenantId $($PSItem.TenantId) |
Where-Object {
$PSItem.StrongAuthenticationRequirements.State -eq $null -and
$PSItem.isLicensed -eq 'TRUE'
}
).UserPrincipalName
)
}
}

Related

Powershell - Combining Variables Properties from AD output

First script I have tried to put together. Im trying to get a new variable with ad user name and ad computer by comparing user name property and description properties. I don't know how to pull the properties I want into the new variables based on a compare-object or match. The description property has a setup of username - ######## numbers very.
Variables used (date tell expire)
$SevenDayWarnDate, $ThreeDayWarnDate, $OneDayWarnDate
AD user
$7, $3, $1 -properties "Name", "PasswordExpiry
AD computer
$comp "Name", "Description"
I was then going to make a pop up on user computer based on expiring passwords.
Below is what I was trying to do but im not sure if the needed information was passed as computer filed comes back empty.
$SevenDayWarnDate = (get-date).adddays(7).ToLongDateString()
$7= Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False -and PasswordLastSet -gt 0 } `
-Properties "Name", "msDS-UserPasswordExpiryTimeComputed" | Select-Object -Property "Name", `
#{Name = "PasswordExpiry"; Expression = {[datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed").tolongdatestring() }} `
|Where-object -Property PasswordExpiry -EQ $SevenDayWarnDate
$comp = Get-Adcomputer -Filter {Enabled -eq $True} -SearchBase "OU=,DC=" -properties "Name", "Description" `
| Select-Object -Property "Name", "Description"
Compare-Object -ReferenceObject $7 -DifferenceObject $comp -IncludeEqual -ExcludeDifferent -PassThru |
ForEach-Object {
[PSCustomObject]#{
Name = $_.name
Computer = ($comp.name | Where-Object Description -match $_.name).Directory
}
}
Working code based on Santiago Squarzon below.
$dayArray= #()
$dayArray=#(7,3,1)
foreach ($day in $dayArray)
{
$SevenDayWarnDate = (get-date).adddays($day).ToLongDateString()
$filter = "Enabled -eq '$True' -and PasswordNeverExpires -eq '$False' -and PasswordLastSet -gt '0'"
$computerArray= #()
$users = Get-ADUser -Filter $filter -Properties "Name", "msDS-UserPasswordExpiryTimeComputed" |
Select-Object Name, #{
Name = "PasswordExpiry"
Expression =
{
[datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed").tolongdatestring()
}
} | Where-object -Property PasswordExpiry -EQ $SevenDayWarnDate
# => It might be better to use:
# PasswordExpiry -ge [datetime]::Now -and PasswordExpiry -le $sevenDayWarnDate
# Find the computers each user is using
$result = foreach($user in $users)
{
$temp=$user.Name
if ($comp = Get-ADComputer -Filter "Description -like '*$temp*'" -Properties Description)
{
[PSCustomObject]#{
Name = $user.Name
PasswordExpiry = $user.PasswordExpiry
ComputerName = $comp.Name
ComputerDescription = $comp.Description
}
$tmpArray= #()
$tmpArray= $comp.Name.Split(" ")
foreach($item in $tmparray)
{
$computerArray += $item
}
$tmpArray = $Null
# }
}
continue
}
foreach($computer in $computerArray)
$tmpMessage =
$tmpMessageTitle =
{Send-RDUserMessage -HostServer $env:COMPUTERNAME -UnifiedSessionID 1 -MessageTitle $tmpMessageTitle -MessageBody $tmpMessage
}
$result | Format-Table
}
Based on the comments and the code in question, I'm guessing this is what you're looking for. There is no need to use Compare-Object, you can simply query Active Directory to get the user's computer based on the Description property.
$SevenDayWarnDate = [datetime]::Now.AddDays(7)
$filter = "Enabled -eq '$True' -and PasswordNeverExpires -eq '$False' -and PasswordLastSet -gt '0'"
$users = Get-ADUser -Filter $filter -Properties "Name", "msDS-UserPasswordExpiryTimeComputed" |
Select-Object Name, #{
Name = "PasswordExpiry"
Expression = {
[datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed")
}
} | Where-object -Property PasswordExpiry -EQ $SevenDayWarnDate
# => It might be better to use:
# {$_.PasswordExpiry -ge [datetime]::Now -and $_.PasswordExpiry -le $sevenDayWarnDate}
# Find the computers each user is using
$result = foreach($user in $users)
{
if($comp = Get-ADComputer -LDAPFilter "(description=$($user.Name))" -Properties Description)
{
[PSCustomObject]#{
Name = $user.Name
PasswordExpiry = $user.PasswordExpiry
ComputerName = $comp.Name
ComputerDescription = $comp.Description
}
continue
}
Write-Host "No computer was found for User: $($user.Name)"
}
$result | Format-Table

Combining 2 cmdlets for one result/output using pscustomobject

Basically what i'm trying to achieve here is an output with 4 column/list (in this case i'm exporting as a text)
Get-MailboxPermission gives me a property of identity, user, accessrights but it doesn't give me a property of "Manager". I need to identify where that particular user reports to. So, i tried PSCustomObject and hoping i can put the results in an array. See script below
$GETMAILBOXPERM = Get-Content C:\Users\Account\Desktop\MailboxUsers\MAILBOXESUSERS.txt | ForEach-Object {Get-MailboxPermission $_ |
where {
($_.User -notlike ‘*NT AUTHORITY*’) -and
($_.User -notlike ‘*S-1-5-21-*’) -and
($_.User -notlike ‘*NAMPRD08*’) -and
($_.User -notlike ‘*PRDTSB01*’) -and
($_.User -notlike ‘*0365Admin*’) -and
($_.User -notlike ‘*Discovery Management*’) -and
($_.User -notlike ‘*NAMPR08A005*’) -and
($_.User -notlike ‘*NT AUTHORITY*’)
}
}
$Results = foreach( $Mailbox in (get-content C:\Users\Account\Desktop\MailboxUsers\MAILBOXESUSERS.txt))
{
$Users = Get-User $Mailbox
if ($Users){
foreach ($User in $Users){
[pscustomobject]#{
DisplayName = $User.name
Account = $GETMAILBOXPERM.user
Manager = $User.manager
Access = $GETMAILBOXPERM.accessrights
}
}
}
}
$Results | Format-List -Property DisplayName, Account, Manager, Access | Out-File C:\Users\Account\Desktop\MailboxUsers\mailbox4.txt
Here's the output in text file. I get the DisplayName and Manager right but the Account and Access just doesn't seem to loop from the text file.
DisplayName : MAILBOX1
Account : {user1#domain.ca, user2#domain.ca, user3#domain.ca, user4#domain.ca...}
Manager : MANAGER1
Access : {FullAccess, FullAccess, FullAccess, FullAccess...}
DisplayName : MAILBOX2
Account : {user1#domain.ca, user2#domain.ca, user3#domain.ca, user4#domain.ca...}
Manager : MANAGER2
Access : {FullAccess, FullAccess, FullAccess, FullAccess...}
The user manager attribute is normally in ADDS, not Exchange. Yet, that text file seems to be where you are getting this from vs dynamically from ADDS.
Why are you using Format-List?
PowerShell will automatically format as a list the moment you columns exceed 5.
This is untested, since I do not have an environment to try it on, but a refactor of what you have here. Give it a shot.
$GetMailboxPerm = Get-Content -Path 'C:\Users\Account\Desktop\MailboxUsers\MAILBOXESUSERS.txt' |
ForEach-Object {Get-MailboxPermission $PSitem |
where {
($PSitem.User -notlike ‘*NT AUTHORITY*|
*S-1-5-21-*|
*NAMPRD08*|
*PRDTSB01*|*0365Admin*|
*Discovery Management*|
*NAMPR08A005*|
*NT AUTHORITY*’)
}
}
foreach( $Mailbox in (Get-Content -Path 'C:\Users\Account\Desktop\MailboxUsers\MailboxUsers.txt'))
{
$Users = Get-User $Mailbox
if ($Users)
{
foreach ($User in $Users)
{
[pscustomobject]#{
DisplayName = $User.name
Account = $GetMailboxPerm.user
Manager = $User.manager
Access = $GetMailboxPerm.accessrights
} | Out-File -FilePath 'C:\Users\Account\Desktop\MailboxUsers\mailbox4.txt' -Append
}
}
}

powershell get-distributiongroupmember

I am trying to get all members of a distribution group in office365 that are in two groups one. The first group "Precon" filters users that are in a specific group. The next group "Office" gets the office distribution group that the memebers are in. The end goal is that I would like to get the office location for each member in the precon group.
I have tried a couple of things but dont know if I have the correct syntax or if what I was trying even works the way I think it does.
So far this is what I have
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $cred -Authentication Basic -AllowRedirection
Import-PSSession $Session -DisableNameChecking
$groupmembers = foreach($g in Get-DistributionGroup -Filter {name -like "Precon"}) {
Get-DistributionGroupMember -Identity $g.SamAccountName | select Name
}
$office = foreach($o in Get-DistributionGroup -Filter {name -like "*Office"})
{
Get-DistributionGroupMember -Identity $o.SamAccountName | select Name
}
$match = $groupmembers | ForEach {
if ($_ -imatch $office){
Write-Output $_ "matches"
}
}
$match
Any help would be appreciated.
What is the output?
For the last block of code I'd look at:
$match = $groupmembers | where $office -contains $_
$match
I found a method that works
$precon = #()
foreach($i in Get-DistributionGroup -Filter {Name -like '*Precon'}) {
$precon += ( Get-DistributionGroupMember -Identity $i.samAccountName | foreach {$_.Name} )
}
$office = #{}
foreach($i in Get-DistributionGroup -Filter {Name -like '*Office'}) {
$office[$i.name] += ( Get-DistributionGroupMember -Identity $i.samAccountName | foreach {$_.Name} )
}
$uber = #()
foreach ($key in $office.Keys) {
$preconOffice = $office[$key] | Where { $precon -Contains $_ }
foreach ($name in $preconOffice) { $uber += ( New-Object -TypeName PSObject -Prop (#{'Office' = $key ;'Name' = $name}) ) }
}
$uber

Using Powershell with large O365 Tenants

I have been tasked with creating a powershell script that will crawl through one of our clients Office365 enviroments daily and disable POP/IMAP Access to anyone not in a specific security group.
I have written the code and it works, up until it gets about 75% of the way through our ~5000+ mailboxes, then it starts to fail with "Starting a command on the remote server failed with the following error message : The I/O operation has been aborted because of either a thread exit or an application request. For more"
I imagine it is somehow timing out due to there being so many mailboxes, but not sure how to more efficiently write my script? Any ideas?
Connect-EXOPSSession
Connect-MSOLService
$PopGroup = Get-MSOLGroup -All | Where-Object {$_.DisplayName -eq "POP Exception"}
$ImapGroup = Get-MSOLGroup -All | Where-Object {$_.DisplayName -eq "IMAP Exception"}
$EnablePOP = Get-MSOLGroupMember -GroupObjectId $PopGroup.ObjectId -All | Select-Object -ExpandProperty DisplayName
$EnableImap = Get-MSOLGroupMember -GroupObjectId $ImapGroup.ObjectId -All | Select-Object -ExpandProperty DisplayName
$Mailboxes = $Mailboxes = Get-Mailbox -ResultSize Unlimited
ForEach ($Mailbox in $Mailboxes) {
If ($EnablePop -Contains $Mailbox) {
$Mailbox | Set-CASMailbox -PopEnabled $True }
Else {
$Mailbox | Set-CASMailbox -PopEnabled $False }
If ($EnableImap -Contains $Mailbox) {
$Mailbox | Set-CASMailbox -ImapEnabled $True }
Else {
$Mailbox | Set-CASMailbox -ImapEnabled $False }}
That error message appears to be a performance limitation or I/O timeout (see this blog entry from MS for example of this issue). I have a couple of ideas to try:
Your Get-MSOLGroup -ALL commands are pulling the entire list of AD groups and then filtering, it would be better to specify the UPN or SearchString to filter your request. See this link for examples and common performance tips for PS. Try $PopGroup = Get-MSOLGroup -SearchString "POP Exception"
Try using Get-CASMailbox instead of getting all mailboxes with all of their data:
Get-CASMailbox -ResultSize Unlimited | foreach-object {
$upn = "$_#YOURDOMAIN.COM"
if($_.POPEnabled -eq $true -and $(!($EnablePop.EmailAddress -Contains $upn))){
Set-CASMailbox -Identity $upn -PopEnabled $false
} elseif ($_.POPEnabled -eq $false -and $EnablePop -Contains $upn){
Set-CASMailbox -Identity $upn -PopEnabled $true
}
if($_.ImapEnabled -eq $true -and $(!($EnableImap -Contains $upn))){
Set-CASMailbox -Identity $upn -ImapEnabled $false
} elseif($_.ImapEnabled -eq $false -and $EnablePop -Contains $upn){
Set-CASMailbox -Identity $upn -ImapEnabled $true
}
}

PowerShell processing of large users very slow - Is there a better way?

I have been using for Office365 Licence Tracking. Actually it looks like good but but it takes too much time to complete the process. most of time is spent by Get-MsolUser it may be improved calculating them in parallel (while processing user 1 you're already fetching user 2's data and so on...) BTW we have about 3000+ cloud user How can I improve the speed of the script?
$T1 = #()
$O365Users = Get-MsolUser -All
ForEach ($O365User in $O365Users)
{
$ADuser = Get-ADUser -Filter { UserPrincipalName -eq $O365User.UserPrincipalName } -Properties whenCreated, Enabled, lastlogondate
$O365Stats = Get-MailboxStatistics $O365User.DisplayName -ErrorAction SilentlyContinue
$O365Smtp = Get-Recipient $O365User.DisplayName -ErrorAction SilentlyContinue
If ($O365Stats -and $O365Smtp) {
If (($ADUser.Enabled -eq $true) -and ($O365User.isLicensed -eq $true))
{
$T1 += New-Object psobject -Property #{
CollectDate = $(Get-Date);
ADUserUPN = $($ADUser.UserPrincipalName);
O365UserUPN = $($O365User.UserPrincipalName);
ADUserCreated = $($ADUser.whenCreated);
ADUserEnabled = $($ADUser.Enabled);
ADLastLogonDate = $($ADUser.LastLogonDate);
O365Licensed = $($O365User.isLicensed);
O365LastLogonTime = $($O365Stats.LastLogonTime);
O365SMTPAddress = $($O365Smtp.PrimarySMTPAddress)
}
}
}
}
$T1 = $T1 | Sort-Object -Property ADUserCreated
$T1 | Format-Table
$T1 | Export-Csv -Path $OutputFile -NoTypeInformation
Write-Host "Output to $OutputFile"
Using a pipeline, filtering early on, and avoiding appending to an array should already speed things up considerably:
Get-MsolUser -All | Where-Object {
$_.IsLicensed
} | ForEach-Object {
$upn = $_.UserPrincipalName
Get-ADUser -Filter "UserPrincipalName -eq '$upn'" -Properties whenCreated, Enabled, lastlogondate
} | Where-Object {
$_.Enabled
} | ForEach-Object {
$O365Stats = Get-MailboxStatistics $_.DisplayName -ErrorAction SilentlyContinue
$O365Smtp = Get-Recipient $_.DisplayName -ErrorAction SilentlyContinue
if ($O365Stats -and $O365Smtp) {
New-Object -Type PSObject -Property #{
'CollectDate' = Get-Date
'ADUserUPN' = $_.UserPrincipalName
'O365UserUPN' = $_.UserPrincipalName
'ADUserCreated' = $_.whenCreated
'ADUserEnabled' = $_.Enabled
'ADLastLogonDate' = $_.LastLogonDate
'O365Licensed' = $true
'O365LastLogonTime' = $O365Stats.LastLogonTime
'O365SMTPAddress' = $O365Smtp.PrimarySMTPAddress
}
}
} | Sort-Object -Property ADUserCreated | Export-Csv -Path $OutputFile -NoType
Also, why the heck is everybody so infatuated with subexpressions? Use them where you need them. Don't obfuscate your code with them when they're unnecessary.
To give you a set off with parallelism in Powershell.
I would like you to go through the PS Workflows.
We have -parallel in that which will help you in parallel call.
Apart from that, we have one function for Invoke-Parallel
This is the link for it : Invoke-Parallel Function
Note: Examples are mentioned inside the function itself . You can use get-help with that function also once compiled.