├── SampleMigrationScripts ├── Set-RoleAssignmentPolicy.ps1 ├── Monitor-MoveStats.ps1 ├── Set-MailboxTimeZone.ps1 ├── Resume-FirstFiveSuspended.ps1 ├── Set-RetentionPolicy.ps1 └── Set-RemoteRoutingAddress.ps1 ├── README ├── PSCredentials.psm1 ├── Office365PowershellUtils.psd1 └── Office365PowerShellUtils_mod.psm1 /SampleMigrationScripts/Set-RoleAssignmentPolicy.ps1: -------------------------------------------------------------------------------- 1 | $mbs = Get-Mailbox 2 | $mbs | Set-Mailbox -RoleAssignmentPolicy "WTA Users" 3 | -------------------------------------------------------------------------------- /SampleMigrationScripts/Monitor-MoveStats.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whatcomtrans/Office365PowershellUtils/HEAD/SampleMigrationScripts/Monitor-MoveStats.ps1 -------------------------------------------------------------------------------- /SampleMigrationScripts/Set-MailboxTimeZone.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whatcomtrans/Office365PowershellUtils/HEAD/SampleMigrationScripts/Set-MailboxTimeZone.ps1 -------------------------------------------------------------------------------- /SampleMigrationScripts/Resume-FirstFiveSuspended.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whatcomtrans/Office365PowershellUtils/HEAD/SampleMigrationScripts/Resume-FirstFiveSuspended.ps1 -------------------------------------------------------------------------------- /SampleMigrationScripts/Set-RetentionPolicy.ps1: -------------------------------------------------------------------------------- 1 | $mbs = Get-Mailbox 2 | $mbs | Set-Mailbox -RetentionPolicy "WTA Primary" 3 | $mbs | ForEach-Object {Start-ManagedFolderAssistant -Identity ($_.Alias)} -------------------------------------------------------------------------------- /SampleMigrationScripts/Set-RemoteRoutingAddress.ps1: -------------------------------------------------------------------------------- 1 | #while ($true) { 2 | $RemoteMailboxes = Get-RemoteMailbox | Where-Object {$_.RemoteRoutingAddress -like '*@whatcomtrans.net'} 3 | $RemoteMailboxes | ForEach-Object {Set-RemoteMailbox -identity ($_.UserPrincipalName) -RemoteRoutingAddress ($_.SamAccountName + '@whatcomtrans.mail.onmicrosoft.com') -Verbose} 4 | . .\Office365\Run-DirSync.ps1 5 | Sleep 300 6 | #} -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | A module for manipulating Office 365. It is being used/tested in a agency with about 100 accounts with DirSync and ADFS Single-Sign on. 2 | 3 | DirSync Script 4 | WTA utilizes a script that is scheduled to run based on the DirSync completion event. It is trigerd based on the Application event log recieving EventID 114 from Application "Directory Synchronization". 5 | 6 | This script which uses saved credentials performs a number of tasks setting up defaults and managing licensing. This is a redacted example of the script. 7 | 8 | Echo "MSOLUserSync version 20140210" 9 | Import-Module MSOnline; 10 | Import-Module ActiveDirectory; 11 | Import-Module 'C:\ScheduledTasks\PoSH_Modules\Office365PowershellUtils\Office365PowerShellUtils.psd1'; 12 | Echo "Creating credential" 13 | $office365cred= Import-PSCredential -Path C:\ScheduledTasks\wtaadmin_as_office365admin.enc.xml; 14 | Echo "Connecting to Office365" 15 | Connect-Office365 -Credential $office365cred; 16 | $licensGroups = Get-ADGroup -Filter {SamAccountName -like "SLIC-MSOL-*"} 17 | Echo "Updating usagelocation" 18 | $licensGroups | Update-MsolUserUsageLocation -UsageLocation @('US') -Confirm:$false; 19 | Echo "Updating licensed users" 20 | #Get list of license changes 21 | [Object[]] $licChanges = $licensGroups | Update-MsolLicensedUsersFromGroup -Property info -OutputOnly; 22 | 23 | #For any existing users that are having a license removed, suspend their mailbox 24 | Echo "Suspending user mailboxs" 25 | $licChanges | Where-Object -Property ChangeType -Value Remove -EQ | ForEach-Object {$_.UserPrincipalName} | Suspend-UserMailbox 26 | 27 | #Output changes 28 | Echo "What commands should be ran do to license changes" 29 | $licChanges | ForEach-Object {Echo $_.Command} 30 | 31 | #Invoke those changes 32 | Echo "Invoking commands" 33 | $licChanges | ForEach-Object {$_.Invoke()} 34 | 35 | #For any users that are having a license added back, see if they have a suspended user mailbox and if so, resume 36 | Echo "Resuming user mailboxs that had been suspended" 37 | $licChanges | Where-Object -Property ChangeType -Value Add -EQ | ForEach-Object {$_.UserPrincipalName} | ForEach-Object {if(Test-Mailbox $_) {Resume-UserMailbox $_}} 38 | 39 | Echo "Syncing user EmailAddress as default SMTP proxyAddress" 40 | Get-ADUser -Filter ("EmailAddress -like '*@*'") | Sync-ProxyAddress -Confirm:$false; 41 | Echo "" 42 | Echo "Setting mailbox timezone" 43 | Get-Mailbox -Filter {RecipientTypeDetails -eq 'UserMailbox'} | Set-MailboxRegionalConfiguration -TimeZone "Pacific Standard Time" –Language en-US; 44 | Echo "Done" 45 | 46 | Needs work. 47 | -------------------------------------------------------------------------------- /PSCredentials.psm1: -------------------------------------------------------------------------------- 1 | # Author: Hal Rottenberg 2 | # Url: http://halr9000.com/article/tag/lib-authentication.ps1 3 | # Purpose: These functions allow one to easily save network credentials to disk in a relatively 4 | # secure manner. The resulting on-disk credential file can only [1] be decrypted 5 | # by the same user account which performed the encryption. For more details, see 6 | # the help files for ConvertFrom-SecureString and ConvertTo-SecureString as well as 7 | # MSDN pages about Windows Data Protection API. 8 | # [1]: So far as I know today. Next week I'm sure a script kiddie will break it. 9 | # 10 | # Usage: Export-PSCredential [-Credential ] [-Path ] 11 | # Export-PSCredential [-Credential ] [-Path ] 12 | # If Credential is not specififed, user is prompted by Get-Credential cmdlet. 13 | # If a username is specified, then Get-Credential will prompt for password. 14 | # If the Path is not specififed, it will default to "./credentials.enc.xml". 15 | # Output: FileInfo object referring to saved credentials 16 | # 17 | # Import-PSCredential [-Path ] 18 | # 19 | # If not specififed, Path is "./credentials.enc.xml". 20 | # Output: PSCredential object 21 | 22 | function Export-PSCredential { 23 | [CmdletBinding(SupportsShouldProcess=$false)] 24 | param ( $Credential = (Get-Credential), $Path = "credentials.enc.xml" ) 25 | 26 | # Look at the object type of the $Credential parameter to determine how to handle it 27 | switch ( $Credential.GetType().Name ) { 28 | # It is a credential, so continue 29 | PSCredential { continue } 30 | # It is a string, so use that as the username and prompt for the password 31 | String { $Credential = Get-Credential -credential $Credential } 32 | # In all other caess, throw an error and exit 33 | default { Throw "You must specify a credential object to export to disk." } 34 | } 35 | 36 | # Create temporary object to be serialized to disk 37 | $export = "" | Select-Object Username, EncryptedPassword 38 | 39 | # Give object a type name which can be identified later 40 | $export.PSObject.TypeNames.Insert(0,'ExportedPSCredential') 41 | 42 | $export.Username = $Credential.Username 43 | 44 | # Encrypt SecureString password using Data Protection API 45 | # Only the current user account can decrypt this cipher 46 | $export.EncryptedPassword = $Credential.Password | ConvertFrom-SecureString 47 | 48 | # Export using the Export-Clixml cmdlet 49 | $export | Export-Clixml $Path 50 | Write-Host -foregroundcolor Green "Credentials saved to: " -noNewLine 51 | 52 | # Return FileInfo object referring to saved credentials 53 | Get-Item $Path 54 | } 55 | 56 | function Import-PSCredential { 57 | [CmdletBinding(SupportsShouldProcess=$false)] 58 | param ( $Path = "credentials.enc.xml" ) 59 | 60 | # Import credential file 61 | $import = Import-Clixml $Path 62 | 63 | # Test for valid import 64 | if ( !$import.UserName -or !$import.EncryptedPassword ) { 65 | Throw "Input is not a valid ExportedPSCredential object, exiting." 66 | } 67 | $Username = $import.Username 68 | 69 | # Decrypt the password and store as a SecureString object for safekeeping 70 | $SecurePass = $import.EncryptedPassword | ConvertTo-SecureString 71 | 72 | # Build the new credential object 73 | $Credential = New-Object System.Management.Automation.PSCredential $Username, $SecurePass 74 | Write-Output $Credential 75 | } 76 | 77 | Export-ModuleMember -Function "Export-PSCredential", "Import-PSCredential" 78 | -------------------------------------------------------------------------------- /Office365PowershellUtils.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'Office365PowershellUtils' 3 | # 4 | # Generated by: R. Josh Nylander 5 | # 6 | # Generated on: 5/8/2020 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | # RootModule = '' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '3.0.0.7' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = 'c6b26555-2b5f-45bc-affe-ef1c31580df3' 22 | 23 | # Author of this module 24 | Author = 'R. Josh Nylander' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'Whatcom Transportation Authority' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) 2012 WTA. All rights reserved.' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'A collection of cmdlets for managing Office365' 34 | 35 | # Minimum version of the PowerShell engine required by this module 36 | PowerShellVersion = '3.0' 37 | 38 | # Name of the PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # ClrVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @('ActiveDirectory','MSOnline','ExchangeOnlineManagement','MicrosoftTeams', 'Microsoft.Online.SharePoint.PowerShell') 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | # RequiredAssemblies = @() 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | NestedModules = @('Office365PowerShellUtils_mod.psm1', 70 | 'PSCredentials.psm1') 71 | 72 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 73 | FunctionsToExport = 'Force-DirSync', 'Add-ProxyAddress', 'Clear-MailboxMemberOf', 74 | 'Connect-Office365', 'Disable-SecurityGroupAsDistributionGroup', 75 | 'Enable-SecurityGroupAsDistributionGroup', 'Export-PSCredential', 76 | 'Find-MsolUsersWithLicense', 'Get-MailboxMemberOf', 'Get-NextDirSync', 77 | 'Get-ProxyAddressDefault', 'Import-PSCredential', 78 | 'Remove-ProxyAddress', 'Resume-UserMailbox', 'Set-ProxyAddress', 79 | 'Start-DirSync', 'Suspend-UserMailbox', 'Sync-ProxyAddress', 80 | 'Test-Mailbox', 'Test-ProxyAddress', 81 | 'Update-MsolLicensedUsersFromGroup', 'Update-MsolUserUsageLocation' 82 | 83 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 84 | CmdletsToExport = @() 85 | 86 | # Variables to export from this module 87 | VariablesToExport = '*' 88 | 89 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 90 | AliasesToExport = @("Force-DirSync") 91 | 92 | # DSC resources to export from this module 93 | # DscResourcesToExport = @() 94 | 95 | # List of all modules packaged with this module 96 | # ModuleList = @() 97 | 98 | # List of all files packaged with this module 99 | # FileList = @() 100 | 101 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 102 | PrivateData = @{ 103 | 104 | PSData = @{ 105 | 106 | # Tags applied to this module. These help with module discovery in online galleries. 107 | # Tags = @() 108 | 109 | # A URL to the license for this module. 110 | # LicenseUri = '' 111 | 112 | # A URL to the main website for this project. 113 | # ProjectUri = '' 114 | 115 | # A URL to an icon representing this module. 116 | # IconUri = '' 117 | 118 | # ReleaseNotes of this module 119 | # ReleaseNotes = '' 120 | 121 | # Prerelease string of this module 122 | # Prerelease = '' 123 | 124 | # Flag to indicate whether the module requires explicit user acceptance for install/update/save 125 | # RequireLicenseAcceptance = $false 126 | 127 | # External dependent modules of this module 128 | # ExternalModuleDependencies = @('ActiveDirectory','MSOnline','ExchangeOnlineManagement','MicrosoftTeams', 'Microsoft.Online.SharePoint.PowerShell') 129 | 130 | } # End of PSData hashtable 131 | 132 | } # End of PrivateData hashtable 133 | 134 | # HelpInfo URI of this module 135 | # HelpInfoURI = '' 136 | 137 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 138 | # DefaultCommandPrefix = '' 139 | 140 | } 141 | 142 | -------------------------------------------------------------------------------- /Office365PowerShellUtils_mod.psm1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Filters objects returned by Get-MsolUser for users which have a particular license AccountSkuId. Requires MSOnline module and active connection to MSOL services. 4 | 5 | .EXAMPLE 6 | Get-MsolUser | Find-MsolUsersWithLicense "whatcomtrans:ENTERPRISEPACK" 7 | #> 8 | function Find-MsolUsersWithLicense { 9 | [CmdletBinding(SupportsShouldProcess=$false)] 10 | Param( 11 | [Parameter(Mandatory=$true,Position=0,HelpMessage="Enter the AccountSkuId to filter users for (see Get-MsolAccountSku).")] [string]$AccountSkuId, 12 | [Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=1,HelpMessage="Object from Get-MsolUser.")] [Object]$msoluser 13 | ) 14 | Process { 15 | foreach ($lic in $msoluser.Licenses) { 16 | Write-Debug $lic 17 | if ($lic.AccountSkuId -eq $AccountSkuId) { 18 | $msoluser 19 | } 20 | } 21 | } 22 | } 23 | 24 | <# 25 | .SYNOPSIS 26 | Performs one-way sync of user license assignment with group membership. Requires ActiveDirectory and MSOnline modules and active connection to MSOL services. 27 | 28 | .EXAMPLE 29 | TODO 30 | #> 31 | function Update-MsolLicensedUsersFromGroup { 32 | [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High",DefaultParameterSetName="SKUFromList")] 33 | Param( 34 | [Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0,HelpMessage="Group(s)")] [Object[]]$ADGroup, 35 | [Parameter(Mandatory=$true,Position=1,ParameterSetName="SKUFromList",HelpMessage="The AccountSkuId(s) to match group membership against(see Get-MsolAccountSku). If more then one value provided they are stepped through for each group provided. If all groups should reference one sku, provide only one. If each group should match a different sku, provide a sku for each group in same order of groups.")] [String[]]$AccountSkuId, 36 | [Parameter(Mandatory=$true,Position=1,ParameterSetName="SKUFromGroup",HelpMessage="The AD Group property to retrieve the AccountSkuId to match group membership against(see Get-MsolAccountSku). Use instead of AccountSkuID. Defaults to the info/note attribute.")] [alias("Property")] [Object]$GroupSKUProperty = "info", 37 | [Parameter(Mandatory=$false,Position=2,HelpMessage="Output change objects instead of making the changes. This causes the cmdlet to return an array of objects which contain the script to run, type of change, etc.")] [Switch]$OutputOnly 38 | ) 39 | Begin { 40 | $groupCounter = 0 41 | if ($AccountSkuId) { 42 | #use array 43 | $skuByArray = $true 44 | } else { 45 | #use group property 46 | $skuByArray = $false 47 | } 48 | $changes = @{} 49 | $skuid = "" 50 | $groupsUsers = @() 51 | $msolLicUsers = "" 52 | 53 | $ChangeTypeScriptBlock = { 54 | if ($this.RemoveSkuID -ne "" -and $this.AddSkuID -eq "") { 55 | Return "Remove" 56 | } elseif ($this.RemoveSkuID -ne "" -and $this.AddSkuID -ne "") { 57 | Return "Transfer" 58 | } elseif ($this.RemoveSkuID -eq "" -and $this.AddSkuID -ne "") { 59 | Return "Add" 60 | } elseif ($this.RemoveSkuID -eq "" -and $this.AddSkuID -eq "") { 61 | Return "None" 62 | } 63 | } 64 | 65 | $SortOrderScriptBlock = { 66 | switch ($this.ChangeType) { 67 | Remove {Return 1} 68 | Transfer {Return 2} 69 | Add {Return 3} 70 | default {Return 0} 71 | } 72 | } 73 | 74 | $CommandScriptBlock = { 75 | switch ($this.ChangeType) { 76 | Remove {Return "Set-MsolUserLicense -UserPrincipalName " + $this.UserPrincipalName + " -RemoveLicenses " + $this.RemoveSkuID} 77 | Transfer {Return "Set-MsolUserLicense -UserPrincipalName " + $this.UserPrincipalName + " -AddLicenses " + $this.AddSkuID + " -RemoveLicenses " + $this.RemoveSkuID} 78 | Add {Return "Set-MsolUserLicense -UserPrincipalName " + $this.UserPrincipalName + " -AddLicenses " + $this.AddSkuID} 79 | default {Return "#No change for user: " + $this.UserPrincipalName} 80 | } 81 | } 82 | } 83 | Process { 84 | #For each group 85 | foreach($valADGroup in $ADGroup) { 86 | #Get ADGroup, but first make sure it is null so we can detect failure 87 | $objADGroup = $null 88 | #If value is a string or somother object passable to Get-ADGroup 89 | if ($skuByArray) { 90 | $objADGroup = Get-ADGroup -Identity $valADGroup 91 | } else { 92 | $objADGroup = Get-ADGroup -Identity $valADGroup -Properties $GroupSKUProperty 93 | } 94 | if ($objADGroup) { #Group exists 95 | #Track accross process calls group count 96 | $groupCounter++ 97 | #Get the skuid 98 | if ($skuByArray) { #Using array instead of group property 99 | if ($AccountSkuId[($groupCounter - 1)]) { 100 | #only change skuid if there is antoher value in array, this allows for one skuid to match multiple groups 101 | $newskuid = $AccountSkuId[($groupCounter - 1)] 102 | } 103 | } else { #Using group property instead of array 104 | if ($objADGroup.$GroupSKUProperty) {$newskuid = $objADGroup.$GroupSKUProperty} 105 | } 106 | if ($newskuid) { #Found skuid, could be from last time 107 | if ($newskuid -ne $skuid) { #moving on to new skuid 108 | $skuid = $newskuid 109 | $groupsUsers = @() 110 | #Get licensed users 111 | $msolLicUsers = Get-MsolUser -EnabledFilter EnabledOnly -All | Find-MsolUsersWithLicense $skuid 112 | } 113 | 114 | #Get group members 115 | $groupMembers = Get-ADGroupMember -Identity $objADGroup -Recursive | Get-ADUser -Properties UserPrincipalName 116 | $groupsUsers = $groupsUsers + $groupMembers 117 | 118 | 119 | #Get differences 120 | $compareResults = Compare-Object -ReferenceObject $groupsUsers -DifferenceObject $msolLicUsers -PassThru -Property UserPrincipalName 121 | 122 | foreach ($user in $compareResults) { 123 | if (!$user.UserPrincipalName) { 124 | Write-Verbose "Unable to find UserPrincipalName for $user" 125 | } else { 126 | if (!$changes.ContainsKey($user.UserPrincipalName)) { 127 | $newObj = New-Object -TypeName PSObject -Property @{UserPrincipalName = $user.UserPrincipalName; RemoveSkuID = ""; AddSkuID = ""} 128 | Add-Member -InputObject $newObj -Name 'SortOrder' -Value $SortOrderScriptBlock -MemberType ScriptProperty 129 | Add-Member -InputObject $newObj -Name 'Command' -Value $CommandScriptBlock -MemberType ScriptProperty 130 | Add-Member -InputObject $newObj -Name 'ChangeType' -Value $ChangeTypeScriptBlock -MemberType ScriptProperty 131 | Add-Member -InputObject $newObj -Name 'Invoke' -Value {Invoke-Expression $this.Command -ErrorAction continue} -MemberType ScriptMethod 132 | $changes.Add($user.UserPrincipalName, $newObj) 133 | } 134 | if ($user.SideIndicator -eq '=>') { #remove 135 | ($changes.Item($user.UserPrincipalName)).RemoveSkuID = $skuid 136 | } else { #add 137 | ($changes.Item($user.UserPrincipalName)).AddSkuID = $skuid 138 | } 139 | } 140 | } 141 | } else { 142 | throw "Could not find AccountSkuId with associated group $valADGroup." 143 | } 144 | } else { 145 | throw "Could not find AD group $valADGroup." 146 | } 147 | } 148 | } 149 | End { 150 | $ProcessScriptBlock = { 151 | if ($PSCmdlet.ShouldProcess($_.UserPrincipalName)) { 152 | Invoke-Expression $_.Command -ErrorAction continue 153 | Write-Verbose $_.Command 154 | } else { 155 | Write-Host $_.Command 156 | } 157 | } 158 | 159 | $arrayOfChanges = @() 160 | 161 | #Convert to simple array 162 | $changes.GetEnumerator() | ForEach-Object {$arrayOfChanges += $_.Value} 163 | 164 | #Execute the commands 165 | if ($OutputOnly) { 166 | return $arrayOfChanges | Sort-Object -Property SortOrder 167 | }else { 168 | $arrayOfChanges | Sort-Object -Property SortOrder | ForEach-Object $ProcessScriptBlock 169 | return ($arrayOfChanges | Sort-Object -Property SortOrder) 170 | } 171 | 172 | } 173 | } 174 | 175 | <# 176 | .SYNOPSIS 177 | Performs one-way sync of user usage location based on group membership. Requires ActiveDirectory and MSOnline modules and active connection to MSOL services. 178 | 179 | .EXAMPLE 180 | TODO 181 | #> 182 | function Update-MsolUserUsageLocation { 183 | [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High",DefaultParameterSetName="UsageLocationFromList")] 184 | Param( 185 | [Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0,HelpMessage="Group(s)")] [Object[]]$ADGroup, 186 | [Parameter(Mandatory=$true,Position=1,ParameterSetName="UsageLocationFromList",HelpMessage="The UsageLocation(s) to set for group membership. If more then one value provided they are stepped through for each group provided. If all groups should reference one location, provide only one. If each group should match a different UsageLocation, provide a value for each group in same order of groups.")] [String[]]$UsageLocation, 187 | [Parameter(Mandatory=$true,Position=1,ParameterSetName="UsageLocationFromGroup",HelpMessage="The AD Group property to retrieve the UsageLocation value from. Defaults to the info/note attribute.")] [alias("Property")] [Object]$GroupUsageLocationProperty = "info" 188 | ) 189 | Begin { 190 | $groupCounter = 0 191 | if ($UsageLocation) { 192 | #use array 193 | $locationByArray = $true 194 | } else { 195 | #use group property 196 | $locationByArray = $false 197 | } 198 | $ProcessScriptBlock = { 199 | $cmdString = "Set-MSOLUser -UsageLocation '" + $UsageLocation + "' -UserPrincipalName " + $_.UserPrincipalName 200 | Write-Verbose $cmdString 201 | if ($PSCmdlet.ShouldProcess($_.UserPrincipalName)) { 202 | Invoke-Expression $cmdString -ErrorAction continue 203 | } else { 204 | Write-Host $cmdString 205 | } 206 | } 207 | } 208 | Process { 209 | #TODO Get group and property if using group property value TODO 210 | 211 | foreach($valADGroup in $ADGroup) { 212 | #Get ADGroup, but first make sure it is null so we can detect failure 213 | $objADGroup = $null 214 | #If value is a string or some other object passable to Get-ADGroup 215 | if ($locationByArray) { 216 | $objADGroup = Get-ADGroup -Identity $valADGroup 217 | } else { 218 | $objADGroup = Get-ADGroup -Identity $valADGroup -Properties $GroupSKUProperty 219 | } 220 | if ($objADGroup) { #Group exists 221 | #Track accross process calls group count 222 | $groupCounter++ 223 | #Get the skuid 224 | if ($locationByArray) { #Using array instead of group property 225 | if ($UsageLocation[($groupCounter - 1)]) { 226 | #only change skuid if there is antoher value in array, this allows for one skuid to match multiple groups 227 | $newUsageLocation = $UsageLocation[($groupCounter - 1)] 228 | } 229 | } else { #Using group property instead of array 230 | if ($objADGroup.$GroupUsageLocationProperty) {$newUsageLocation = $objADGroup.$GroupUsageLocationProperty} 231 | } 232 | 233 | Get-ADGroupMember -Identity $objADGroup -Recursive | Get-ADUser -Properties UserPrincipalName | ForEach-Object $ProcessScriptBlock 234 | } 235 | } 236 | } 237 | End { 238 | } 239 | } 240 | 241 | function Change-ProxyAddress { 242 | [CmdletBinding(SupportsShouldProcess=$true,DefaultParameterSetName="Add")] 243 | param( 244 | [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,HelpMessage="Identity of user to change, takes same as Set-ADUser or pipe a User object.")] 245 | $Identity, 246 | [Parameter(Mandatory=$false,Position=1,ValueFromPipeline=$false,HelpMessage="The proxy address to add, without the prefix. Example, johndoe@domain.com")] 247 | [String]$ProxyAddress, 248 | [Parameter(Mandatory=$false,Position=4,ValueFromPipeline=$false,HelpMessage="The prefix. Default is SMTP")] 249 | [String]$Prefix="smtp", 250 | [Parameter(Mandatory=$false,Position=3,ValueFromPipeline=$true,HelpMessage="Identity of user to change, takes same as Set-ADUser or pipe a User object.")] 251 | [Switch] $IsDefault, 252 | [Parameter(Mandatory=$false,Position=2,ValueFromPipeline=$true,ParameterSetName="Add",HelpMessage="Add the ProxyAddress with the specified Prefix.")] 253 | [Switch] $Add, 254 | [Parameter(Mandatory=$false,Position=2,ValueFromPipeline=$true,ParameterSetName="Remove",HelpMessage="Remove the ProxyAddress with the specified Prefix.")] 255 | [Switch] $Remove, 256 | [Parameter(Mandatory=$false,Position=2,ValueFromPipeline=$true,ParameterSetName="Sync",HelpMessage="Sync the list of ProxyAddresses to make sure it includes the user's EmailAddress.")] 257 | [Switch] $Sync, 258 | [Parameter(Mandatory=$false,Position=2,ValueFromPipeline=$true,ParameterSetName="Test",HelpMessage="Test to see of the ProxyAddress with the specified Prefix exists.")] 259 | [Switch] $Test, 260 | [Parameter(Mandatory=$false,Position=2,ValueFromPipeline=$true,ParameterSetName="GetDefault",HelpMessage="Test to see of the ProxyAddress with the specified Prefix exists.")] 261 | [Switch] $GetDefault 262 | ) 263 | 264 | process { 265 | #Handle parameters 266 | $_identity = $Identity 267 | [String] $_changeaddress = $ProxyAddress 268 | [boolean] $_isDefault = $IsDefault 269 | [String] $_prefix = $Prefix 270 | 271 | #Set initial values 272 | [boolean] $_exists = $false 273 | [boolean] $_changed = $false 274 | [int] $_identityType = 0 275 | [String] $_syncAddress = "" 276 | 277 | #First, find the user, group or mailbox to change. Search local AD users, followed by local AD groups and then Mailboxes 278 | try { 279 | $_user = Get-ADUser $_identity -Properties proxyAddresses,mail -ErrorAction SilentlyContinue 280 | }catch {} 281 | if (!$_user) { 282 | try { 283 | $_user = Get-ADGroup $_identity -Properties proxyAddresses,mail -ErrorAction Stop 284 | } catch {} 285 | if (!$_user) { 286 | $_user = Get-Mailbox $_identity 287 | if ($_user) { 288 | $_identityType = 3 289 | } 290 | } else { 291 | $_identityType = 2 292 | } 293 | } else { 294 | $_identityType = 1 295 | } 296 | 297 | #Second, now that we have the "user" object, retrieve the list or proxyAddresses and their current mail address 298 | switch ($_identityType) { 299 | 0 {throw "Unable to find local AD User, Group or remote Mailbox that matches $_identity"} 300 | 1 { 301 | [System.Collections.ArrayList] $_addresses = $_user.proxyAddresses 302 | $_syncAddress = $_user.mail 303 | } 304 | 2 { 305 | [System.Collections.ArrayList] $_addresses = $_user.proxyAddresses 306 | $_syncAddress = $_user.mail 307 | } 308 | 3 { 309 | [System.Collections.ArrayList] $_addresses = $_user.EmailAddresses 310 | $_syncAddress = ($_user.PrimarySmtpAddress) 311 | } 312 | } 313 | 314 | #Third, handle switches 315 | if ($Sync) { 316 | $_isDefault = $true 317 | $_changeaddress = $_syncAddress 318 | } 319 | 320 | if ($_isDefault) { 321 | $_prefix = $_prefix.ToUpper() 322 | } else { 323 | $_prefix = $_prefix.ToLower() 324 | } 325 | 326 | [String] $_changeproxyaddress = $_prefix + ":" + $_changeaddress 327 | 328 | #Fourth, iterate the list of addresses 329 | for ($i=0; $i -lt $_addresses.Count; $i++) { 330 | [String] $_address = $_addresses[$i] 331 | if ($_address -Like ($_prefix + ":*")) { 332 | #Found a proxy address with matching prefix 333 | if ($_address -like $_changeproxyaddress) { 334 | #already exists 335 | #if it should not be the default, change it 336 | if (!$_isDefault -and ($_address -CLike ($_prefix.ToUpper() + ":*"))) { 337 | #found the current default, need to change 338 | $_addresses[$i] = $_address.Replace($_prefix.ToUpper(), $_prefix) 339 | $_changed = $true 340 | } 341 | 342 | #if it should be the default and it is not, change it 343 | if ($_isDefault -and ($_address -CLike ($_prefix.ToLower() + ":*"))) { 344 | #found the current default, need to change 345 | $_addresses[$i] = $_address.Replace($_prefix.ToLower(), $_prefix) 346 | $_changed = $true 347 | } 348 | 349 | #note that it exists so that Add does not run 350 | $_exists = $true 351 | 352 | #if this is a remove process, remove it 353 | if ($Remove) { 354 | #Remove it 355 | $_addresses.RemoveAt($i) 356 | $_changed = $true 357 | } 358 | } else { 359 | #a different proxy with same prefix 360 | if ($_isDefault -and ($_address -CLike ($_prefix + ":*"))) { 361 | #if just looking for default the just return value here 362 | if ($GetDefault) { 363 | return $_address 364 | } 365 | #found the current default, need to change 366 | $_addresses[$i] = $_address.Replace($_prefix, $_prefix.ToLower()) 367 | $_changed = $true 368 | } 369 | } 370 | } 371 | } 372 | 373 | #Fifth, handle the Test and Add switches 374 | if ($Test) { 375 | if ($_exists) { 376 | return $true 377 | } else { 378 | return $false 379 | } 380 | } 381 | 382 | if (($_exists -eq $false) -and ($Add -or $Sync)) { 383 | #proxy not found, add it 384 | $_addresses += $_changeproxyaddress 385 | $_changed = $true 386 | } 387 | 388 | #Sixth, make the changes 389 | [Array] $_changedaddresses = $_addresses.ToArray() 390 | #$_changedaddresses 391 | if ($_changed) { 392 | if ($_addresses.Count -gt 0) { 393 | if ($_identityType -eq 3) { 394 | Set-Mailbox -Identity ($_user.UserPrincipalName) -EmailAddresses $_changedaddresses 395 | } else { 396 | Set-ADObject -Identity $_user -Replace @{proxyAddresses=$_changedaddresses} 397 | } 398 | } else { 399 | if ($_identityType -eq 3) { 400 | Set-Mailbox -Identity ($_user.UserPrincipalName) -EmailAddresses $_changedaddresses 401 | } else { 402 | Set-ADObject -Identity $_user -Clear proxyAddresses 403 | } 404 | } 405 | } 406 | } 407 | } 408 | 409 | <# 410 | .SYNOPSIS 411 | Makes it easy to add a proxyAddress to a user in local Active Directory (used with DirSync). 412 | 413 | .EXAMPLE 414 | Add-ProxyAddress -Identity TestProxy -ProxyAddress "testproxy1@ridewta.com" -IsDefault -Confirm 415 | #> 416 | function Add-ProxyAddress { 417 | [CmdletBinding(SupportsShouldProcess=$true)] 418 | param( 419 | [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,HelpMessage="Identity of user to change, takes same as Set-ADUser or pipe a User object.")] 420 | $Identity, 421 | [Parameter(Mandatory=$true,Position=1,ValueFromPipeline=$false,HelpMessage="The proxy address, without the prefix. Example, johndoe@domain.com")] 422 | [String]$ProxyAddress, 423 | [Parameter(Mandatory=$false,Position=3,ValueFromPipeline=$false,HelpMessage="The proxy address prefix (smtp, sip, x500, etc)")] 424 | [String]$Prefix="smtp", 425 | [Parameter(Mandatory=$false,Position=2,ValueFromPipeline=$true,HelpMessage="Should the proxyAddress be the default.")] 426 | [Switch] $IsDefault 427 | ) 428 | 429 | process { 430 | Change-ProxyAddress -Identity $Identity -ProxyAddress $ProxyAddress -Prefix $Prefix -IsDefault:$IsDefault -Add 431 | } 432 | } 433 | 434 | <# 435 | .SYNOPSIS 436 | Makes it easy to remove a proxyAddress from a user in local Active Directory (used with DirSync). 437 | 438 | .EXAMPLE 439 | Remove-ProxyAddress -Identity TestProxy -ProxyAddress "testproxy1@ridewta.com" -IsDefault -Confirm 440 | #> 441 | function Remove-ProxyAddress { 442 | [CmdletBinding(SupportsShouldProcess=$true)] 443 | param( 444 | [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,HelpMessage="Identity of user to change, takes same as Set-ADUser or pipe a User object.")] 445 | $Identity, 446 | [Parameter(Mandatory=$true,Position=1,ValueFromPipeline=$false,HelpMessage="The proxy address, without the prefix. Example, johndoe@domain.com")] 447 | [String]$ProxyAddress, 448 | [Parameter(Mandatory=$false,Position=2,ValueFromPipeline=$false,HelpMessage="The proxy address prefix (smtp, sip, x500, etc)")] 449 | [String]$Prefix="smtp" 450 | ) 451 | 452 | process { 453 | Change-ProxyAddress -Identity $Identity -ProxyAddress $ProxyAddress -Prefix $Prefix -IsDefault:$false -Remove 454 | } 455 | } 456 | 457 | <# 458 | .SYNOPSIS 459 | Makes it easy to set the default proxyAddress for a user in local Active Directory (used with DirSync). It will add the address if it does not exist. 460 | 461 | .EXAMPLE 462 | Set-ProxyAddress -Identity TestProxy -ProxyAddress "testproxy1@ridewta.com" -IsDefault -Confirm 463 | #> 464 | function Set-ProxyAddress { 465 | [CmdletBinding(SupportsShouldProcess=$true)] 466 | param( 467 | [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,HelpMessage="Identity of user to change, takes same as Set-ADUser or pipe a User object.")] 468 | $Identity, 469 | [Parameter(Mandatory=$true,Position=1,ValueFromPipeline=$false,HelpMessage="The proxy address, without the prefix. Example, johndoe@domain.com")] 470 | [String]$ProxyAddress, 471 | [Parameter(Mandatory=$false,Position=3,ValueFromPipeline=$false,HelpMessage="The proxy address prefix (smtp, sip, x500, etc)")] 472 | [String]$Prefix="smtp", 473 | [Parameter(Mandatory=$false,Position=2,ValueFromPipeline=$true,HelpMessage="Should the proxyAddress be the default.")] 474 | [Switch] $IsDefault 475 | ) 476 | 477 | process { 478 | Change-ProxyAddress -Identity $Identity -ProxyAddress $ProxyAddress -Prefix $Prefix -IsDefault:$IsDefault -Add 479 | } 480 | } 481 | 482 | <# 483 | .SYNOPSIS 484 | Makes it easy to sync a user's EmailAddress with the collection of proxyAddresses. The user's EmailAddress will added if missing and set as the default SMTP ProxyAddress in local Active Directory (used with DirSync). 485 | 486 | .EXAMPLE 487 | Sync-ProxyAddress -Identity TestProxy -Confirm 488 | #> 489 | function Sync-ProxyAddress { 490 | [CmdletBinding(SupportsShouldProcess=$true)] 491 | param( 492 | [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,HelpMessage="Identity of user to change, takes same as Set-ADUser or pipe a User object.")] 493 | $Identity 494 | ) 495 | 496 | process { 497 | Change-ProxyAddress -Identity $Identity -ProxyAddress "" -Prefix "smtp" -IsDefault:$true -Sync 498 | } 499 | } 500 | 501 | <# 502 | .SYNOPSIS 503 | Makes it easy to test if a proxyAddress exists for specified user in local Active Directory (used with DirSync). 504 | 505 | .EXAMPLE 506 | Test-ProxyAddress -Identity TestProxy -ProxyAddress "testproxy1@ridewta.com" 507 | #> 508 | function Test-ProxyAddress { 509 | [CmdletBinding(SupportsShouldProcess=$false)] 510 | param( 511 | [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,HelpMessage="Identity of user to change, takes same as Set-ADUser or pipe a User object.")] 512 | $Identity, 513 | [Parameter(Mandatory=$true,Position=1,ValueFromPipeline=$false,HelpMessage="The proxy address, without the prefix. Example, johndoe@domain.com")] 514 | [String]$ProxyAddress, 515 | [Parameter(Mandatory=$false,Position=2,ValueFromPipeline=$false,HelpMessage="The proxy address prefix (smtp, sip, x500, etc)")] 516 | [String]$Prefix="smtp" 517 | ) 518 | 519 | process { 520 | return Change-ProxyAddress -Identity $Identity -ProxyAddress $ProxyAddress -Prefix $Prefix -Test 521 | } 522 | } 523 | 524 | <# 525 | .SYNOPSIS 526 | Returns the default ProxyAddress for the specified Prefix (defaults to SMTP) in local Active Directory (used with DirSync). 527 | 528 | .EXAMPLE 529 | Get-ProxyAddressDefault -Identity TestProxy 530 | #> 531 | function Get-ProxyAddressDefault { 532 | [CmdletBinding(SupportsShouldProcess=$false)] 533 | param( 534 | [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,HelpMessage="Identity of user to change, takes same as Set-ADUser or pipe a User object.")] 535 | $Identity, 536 | [Parameter(Mandatory=$false,Position=1,ValueFromPipeline=$false,HelpMessage="The proxy address prefix (smtp, sip, x500, etc)")] 537 | [String]$Prefix="smtp" 538 | ) 539 | 540 | process { 541 | return Change-ProxyAddress -Identity $Identity -ProxyAddress "" -Prefix $Prefix -IsDefault -GetDefault 542 | } 543 | } 544 | 545 | <# 546 | .SYNOPSIS 547 | Adds display name and email address to a security group in local Active Directory thus enabling DirSync to add it as an Exchange Online Distribution Group. Really just a shortcut to Set-ADGroup. 548 | 549 | .EXAMPLE 550 | Enable-SecurityGroupAsDistributionGroup -Identity GroupA -DisplayName "Group A" -EmailAddress "groupa@company.com" 551 | #> 552 | function Enable-SecurityGroupAsDistributionGroup { 553 | [CmdletBinding(SupportsShouldProcess=$true)] 554 | param( 555 | [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,HelpMessage="Identity of group to change, takes same as Set-ADGroup or pipe a group object.")] 556 | $Identity, 557 | [Parameter(Mandatory=$true,Position=1,ValueFromPipeline=$false,HelpMessage="The display name (for the address book).")] 558 | [String]$DisplayName, 559 | [Parameter(Mandatory=$true,Position=2,ValueFromPipeline=$false,HelpMessage="The email address for the group.")] 560 | [String]$EmailAddress, 561 | [Parameter(Mandatory=$false,Position=3,ValueFromPipeline=$false,HelpMessage="Hide this group from the address list. Defaults to false.")] 562 | [Switch]$Hide 563 | ) 564 | 565 | process { 566 | $_group = Get-ADGroup -Identity $Identity -Properties DisplayName,mail,msExchHideFromAddressLists 567 | $_group.DisplayName = $DisplayName 568 | $_group.mail = $EmailAddress 569 | if ($Hide) { 570 | $_group.msExchHideFromAddressLists = $true 571 | } elseif ($_group.msExchHideFromAddressLists) { 572 | $_group.msExchHideFromAddressLists = $false 573 | } 574 | Set-ADGroup -Instance $_group 575 | Sync-ProxyAddress $Identity 576 | } 577 | } 578 | 579 | <# 580 | .SYNOPSIS 581 | Removes display name and email address to a security group in local Active Directory thus enabling DirSync to remove it as an Exchange Online Distribution Group. Really just a shortcut to Set-ADGroup. 582 | 583 | .EXAMPLE 584 | Disable-SecurityGroupAsDistributionGroup -Identity GroupA 585 | #> 586 | function Disable-SecurityGroupAsDistributionGroup { 587 | [CmdletBinding(SupportsShouldProcess=$true)] 588 | param( 589 | [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,HelpMessage="Identity of group to change, takes same as Set-ADGroup or pipe a group object.")] 590 | $Identity 591 | ) 592 | 593 | process { 594 | $_group = Get-ADGroup -Identity $Identity -Properties DisplayName,mail 595 | Remove-ProxyAddress $Identity -ProxyAddress $_group.mail 596 | Set-ADGroup -Identity $Identity -Clear DisplayName,mail 597 | } 598 | } 599 | 600 | <# 601 | .SYNOPSIS 602 | Starts directory sync 603 | 604 | .EXAMPLE 605 | Start-DirSync 606 | #> 607 | function Start-DirSync { 608 | [CmdletBinding()] 609 | param( 610 | [Parameter(Mandatory=$false,Position=0,ValueFromPipeline=$false,HelpMessage="ADSync server to invoke command on.")] 611 | [String]$ComputerName 612 | ) 613 | if ($ComputerName.length -ge 1) { 614 | Invoke-Command -ComputerName $ComputerName -ScriptBlock {Import-Module ADSync; Start-ADSyncSyncCycle -PolicyType Delta} 615 | } 616 | } 617 | 618 | Set-Alias -Name Force-DirSync -Value Start-DirSync -Description "Renamed Force-DirSync to Start-DirSync for a more compiant verb. Alias for backards compatibility." 619 | 620 | function Get-NextDirSync { 621 | [CmdletBinding()] 622 | param( 623 | [Parameter(Mandatory=$false,Position=0,ValueFromPipeline=$false,HelpMessage="ADSync server to invoke command on.")] 624 | [String]$ComputerName 625 | ) 626 | if ($ComputerName.length -ge 1) { 627 | Invoke-Command -ComputerName $ComputerName -ScriptBlock {Import-Module ADSync; [DateTime]::Parse((get-adsyncscheduler).NextSyncCycleStartTimeInUTC.ToString() + " GMT");} 628 | } 629 | } 630 | <# 631 | .SYNOPSIS 632 | This suspends a user mailbox by transitioning it to a shared mailbox, hiding it and preventing it from recieving email. 633 | This is used for employees who's role no longer requires email but may again one day. 634 | 635 | .EXAMPLE 636 | Suspend-UserMailbox -Identity UserA 637 | #> 638 | function Suspend-UserMailbox { 639 | [CmdletBinding(SupportsShouldProcess=$true)] 640 | param( 641 | [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,HelpMessage="Mailbox of user to suspend, takes same as Get-Mailbox.")] 642 | [String] $Identity 643 | ) 644 | 645 | process { 646 | $mb = Get-Mailbox $Identity 647 | 648 | #Change mailbox to Shared 649 | Set-Mailbox -Identity ($mb.Alias) -Type Shared -ErrorAction Continue 650 | 651 | #Hide in GAL 652 | Set-ADUser -Identity ($Identity) -Replace @{msExchHideFromAddressLists = $true} 653 | 654 | #Block from recieving email 655 | Set-Mailbox -Identity ($mb.Alias) -AcceptMessagesOnlyFrom "no-reply@ridewta.com" 656 | 657 | #Remove from any AD Groups which are also Distribution Groups 658 | ((Get-ADUser -Identity $Identity -Properties MemberOf).MemberOf | Get-ADGroup) | ForEach-Object {if (Get-DistributionGroup -Identity $_.Name -ErrorAction SilentlyContinue) {Remove-ADGroupMember -Identity ($_.SamAccountName) -Member $Identity -Confirm:$false}} 659 | 660 | #Remove and distribution groups for which the mailbox is a member 661 | Clear-MailboxMemberOf $mb.Alias -Confirm:$false 662 | } 663 | } 664 | 665 | <# 666 | .SYNOPSIS 667 | This reverses the Suspend-UserMailbox cmdlet. 668 | This resumes a user mailbox by transitioning it fram a shared mailbox, un-hiding it and allowing it to recieve email. 669 | This is used for employees who's role once again requires email. 670 | 671 | .EXAMPLE 672 | Resume-UserMailbox -Identity UserA 673 | #> 674 | function Resume-UserMailbox { 675 | [CmdletBinding(SupportsShouldProcess=$true)] 676 | param( 677 | [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,HelpMessage="Mailbox of user to suspend, takes same as Get-Mailbox.")] 678 | [String] $Identity 679 | ) 680 | 681 | process { 682 | $mb = Get-Mailbox $Identity 683 | 684 | #Change mailbox to Regular 685 | Set-Mailbox -Identity ($mb.Identity) -Type Regular -ErrorAction Continue 686 | 687 | #Show in GAL 688 | Set-ADUser -Identity ($mb.Alias) -Replace @{msExchHideFromAddressLists = $false} 689 | 690 | #Allow recieving email 691 | Set-Mailbox -Identity ($mb.Identity) -AcceptMessagesOnlyFrom $null 692 | 693 | #User will need to be added back to any distribution lists via another process 694 | } 695 | } 696 | 697 | 698 | <# 699 | .SYNOPSIS 700 | Tests to see if a mailbox with a given identity exists, returns boolean 701 | 702 | .EXAMPLE 703 | Test-Mailbox -Identity UserA 704 | #> 705 | function Test-Mailbox { 706 | [CmdletBinding(SupportsShouldProcess=$false)] 707 | param( 708 | [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,HelpMessage="Identity to test for.")] 709 | [String] $Identity 710 | ) 711 | 712 | process { 713 | return [bool] (Get-Mailbox $Identity -ErrorAction SilentlyContinue) 714 | } 715 | } 716 | 717 | # use-cas -SkipUpdate 718 | # Connect-Office365 -Credential (Get-Credential) 719 | 720 | 721 | function Get-MailboxMemberOf { 722 | [CmdletBinding(SupportsShouldProcess=$false)] 723 | Param ( 724 | [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 725 | [String] $Identity 726 | ) 727 | Process { 728 | $mb = Get-mailbox $Identity 729 | forEach($g in Get-DistributionGroup) { 730 | Get-DistributionGroupmember $g.Name | Where-Object Guid -eq $mb.Guid | %{Write-Output $g} 731 | } 732 | } 733 | } 734 | 735 | function Clear-MailboxMemberOf { 736 | [CmdletBinding(SupportsShouldProcess=$true)] 737 | Param ( 738 | [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 739 | [String] $Identity 740 | ) 741 | Process { 742 | (Get-MailboxMemberOf $Identity) | Remove-DistributionGroupMember -Member $Identity -BypassSecurityGroupManagerCheck -Confirm:$false 743 | } 744 | } 745 | 746 | # TODO: This cmdlet needs more documentation, better parameter settings and better error checking. 747 | function Connect-Office365 { 748 | [CmdletBinding(SupportsShouldProcess=$false, DefaultParameterSetName="Username")] 749 | Param( 750 | [Parameter(Mandatory=$false,ParameterSetName="Username",HelpMessage="Credentials")] 751 | [String] $Username, 752 | [Parameter(Mandatory=$false,ParameterSetName="Credentials",HelpMessage="Credentials")] 753 | [System.Management.Automation.PSCredential] $Credential, 754 | [Parameter(Mandatory=$false,ParameterSetName="CredentialsFile", HelpMessage="Path to credentials")] 755 | [String] $CredentialPath, 756 | [Parameter(Mandatory=$false)] 757 | [String] $CertificateFilePath, 758 | [Parameter(Mandatory=$false)] 759 | [SecureString] $CertificatePassword, 760 | [Parameter(Mandatory=$false)] 761 | [String] $CertificateThumbPrint, 762 | [Parameter(Mandatory=$false)] 763 | [String] $SPOServiceURL, 764 | [Parameter(Mandatory=$false)] 765 | [String] $AppID, 766 | [Parameter(Mandatory=$false)] 767 | [String] $Organization, 768 | [Parameter(Mandatory=$false)] 769 | [Switch] $AvoidMFA, 770 | [Parameter(Mandatory=$false)] 771 | [Switch] $SkipSharePoint, 772 | [Parameter(Mandatory=$false)] 773 | [Switch] $SkipExchange, 774 | [Parameter(Mandatory=$false)] 775 | [Switch] $SkipTeams, 776 | [Parameter(Mandatory=$false)] 777 | [Switch] $SkipComplianceCenter 778 | ) 779 | 780 | Process { 781 | 782 | #Prompt for credential if not provided 783 | if ($AvoidMFA) { 784 | if ($CredentialPath) { 785 | $Credential = Import-PSCredential -Path $CredentialPath 786 | } 787 | if ($Username) { 788 | $Credential = Get-Credential -UserName $Username -Message "Office 365 Credentials" 789 | } 790 | 791 | if (!$Credential) { 792 | $Credential = Get-Credential 793 | } 794 | } 795 | 796 | #Connect to MSOLService with credential 797 | <# 798 | if ($AvoidMFA) { 799 | Connect-MsolService -Credential $Credential 800 | } else { 801 | Connect-MsolService 802 | } 803 | #> 804 | 805 | #Connect to SharePoint 806 | if (!$SkipSharePoint) { 807 | if ($AvoidMFA) { 808 | Connect-SPOService -Url $SPOServiceURL -Credential $Credential 809 | } else { 810 | Connect-SPOService -Url $SPOServiceURL 811 | } 812 | } 813 | 814 | #Connect to Teams 815 | if (!$SkipTeams) { 816 | if ($AvoidMFA) { 817 | Connect-MicrosoftTeams -Credential $Credential 818 | } else { 819 | Connect-MicrosoftTeams 820 | } 821 | } 822 | 823 | #Connect to Exchange Online 824 | if (!$SkipExchange) { 825 | <# Support for connection via certificate 826 | See https://docs.microsoft.com/en-us/powershell/exchange/app-only-auth-powershell-v2?view=exchange-ps 827 | and https://docs.microsoft.com/en-us/azure/automation/shared-resources/certificates 828 | #> 829 | if ($AvoidMFA) { 830 | if ($AppID) { 831 | if ($CertificateFilePath) { 832 | Connect-ExchangeOnline -CertificateFilePath $CertificateFilePath -CertificatePassword $CertificatePassword -AppId $AppID -Organization $Organization 833 | } elseif ($CertificateThumbPrint) { 834 | Connect-ExchangeOnline -CertificateThumbprint $CertificateThumbPrint -AppId $AppID -Organization $Organization 835 | } else { 836 | Write-Warning "Exchange not loaded. CertificateFilePath or CertificateThumbprint required. See https://docs.microsoft.com/en-us/powershell/exchange/app-only-auth-powershell-v2?view=exchange-ps" 837 | } 838 | } else { 839 | # Connect another way? The old way? Essentialy if AvoidMFA, this currently is same as SkipExchange. 840 | Write-Warning -Message "Exchange not loaded. Currently no none MFA connection to Exchange Online available, skipping connection to Exchange Online" 841 | } 842 | } else { 843 | # https://david-homer.blogspot.com/2025/01/exchange-online-management-powershell.html 844 | $msalPath = [System.IO.Path]::GetDirectoryName((Get-Module ExchangeOnlineManagement).Path); 845 | Add-Type -Path "$msalPath\Microsoft.IdentityModel.Abstractions.dll"; 846 | Add-Type -Path "$msalPath\Microsoft.Identity.Client.dll"; 847 | [Microsoft.Identity.Client.IPublicClientApplication] $application = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create("fb78d390-0c51-40cd-8e17-fdbfab77341b").WithDefaultRedirectUri().Build(); 848 | $result = $application.AcquireTokenInteractive([string[]]"https://outlook.office365.com/.default").ExecuteAsync().Result; 849 | Connect-ExchangeOnline -AccessToken $result.AccessToken -UserPrincipalName $result.Account.Username; 850 | # Connect-ExchangeOnline 851 | } 852 | } 853 | 854 | if (!$SkipComplianceCenter) { 855 | # IPPSSession supports both MFA and basic connect 856 | if ($AvoidMFA) { 857 | # Basic 858 | Connect-IPPSSession -Credential $Credential 859 | } else { 860 | # MFA 861 | Connect-IPPSSession 862 | } 863 | } 864 | } 865 | } 866 | 867 | Export-ModuleMember -Function "Find-MsolUsersWithLicense", "Update-MsolLicensedUsersFromGroup", "Update-MsolUserUsageLocation", "Add-ProxyAddress", "Remove-ProxyAddress", "Set-ProxyAddress", "Sync-ProxyAddress", "Test-ProxyAddress", "Get-ProxyAddressDefault", "Enable-SecurityGroupAsDistributionGroup", "Disable-SecurityGroupAsDistributionGroup", "Start-DirSync", "Suspend-UserMailbox", "Resume-UserMailbox", "Test-Mailbox", "Get-NextDirSync", "Get-MailboxMemberOf", "Clear-MailboxMemberOf", "Use-Office365", "Connect-Office365" -Alias "Force-DirSync" 868 | --------------------------------------------------------------------------------