├── README.md ├── Office365 ├── ExchangeOnline │ ├── Enable-MailboxAuditing │ │ ├── single.ps1 │ │ ├── delegated.ps1 │ │ ├── auzreFunction.ps1 │ │ └── README.md │ ├── Block-ExternalMailForwarding │ │ ├── single.ps1 │ │ ├── delegated.ps1 │ │ ├── azureFunction.ps1 │ │ └── README.md │ ├── Export-UserLicenceList │ │ ├── delegated.ps1 │ │ └── README.md │ ├── Enable-IncreaseE3MailboxSizeTo100GB │ │ ├── single.ps1 │ │ ├── delegated.ps1 │ │ └── README.md │ ├── Export-UserExternalMailForwarding │ │ ├── single.ps1 │ │ ├── delegated.ps1 │ │ └── README.md │ ├── Warning-UserImpersonation │ │ ├── single.ps1 │ │ ├── azureFunctionSingle.ps1 │ │ ├── delegated.ps1 │ │ └── azureFunctionDelegated.ps1 │ └── Export-UserLoginLocation │ │ ├── single.ps1 │ │ └── delegated.ps1 ├── General │ ├── Export-AdministrativeUsers │ │ ├── Block-Administrators.ps1 │ │ ├── Administrators.ps1 │ │ ├── unlicencedAdministrators.ps1 │ │ └── README.md │ └── Enable-AuditLogging │ │ ├── scripts3.ps1 │ │ ├── script2.ps1 │ │ └── script1.ps1 ├── OneDrive │ └── Transfer-OneDrivetoOneDrive │ │ ├── sinlge.ps1 │ │ └── README.md └── SharePoint │ └── Enable-SharePointGraphApplication │ ├── single.ps1 │ └── README.md ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── LICENSE └── ITGlue ├── HaveIBeenPwnedPasswordCheck ├── single.ps1 └── README.md ├── Office365Sync ├── single.ps1 └── auzreFunction.ps1 ├── OrganisationSharePointSync └── single.ps1 ├── SyncMicrosoftSecureScoreReports └── CreateApp.ps1 ├── Office365UserActivityAndUsage └── CreateApp.ps1 └── UniFiSync └── UniFiSharePointMapping.ps1 /README.md: -------------------------------------------------------------------------------- 1 | # Knowledge Base 2 | 3 | Thanks for stopping by! 4 | 5 | This repo contains various scripts and functions found at https://gcits.com/knowledge-base/ 6 | 7 | If you have any queries or recommendations contact us at info@gcits.com 8 | -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Enable-MailboxAuditing/single.ps1: -------------------------------------------------------------------------------- 1 | Get-Mailbox -ResultSize Unlimited | Set-Mailbox -AuditEnabled $true -AuditOwner MailboxLogin, HardDelete, SoftDelete, Update, Move -AuditDelegate SendOnBehalf, MoveToDeletedItems, Move -AuditAdmin Copy, MessageBind -------------------------------------------------------------------------------- /Office365/General/Export-AdministrativeUsers/Block-Administrators.ps1: -------------------------------------------------------------------------------- 1 | cls 2 | 3 | # This is the username of an Office 365 account with delegated admin permissions 4 | 5 | $UserName = "training@gcits.com" 6 | 7 | $Cred = get-credential -Credential $UserName 8 | 9 | $users = import-csv "C:\temp\AdminUserList.csv" 10 | 11 | Connect-MsolService -Credential $cred 12 | 13 | 14 | ForEach ($user in $users) { 15 | 16 | $tenantID = $user.tenantid 17 | 18 | $upn = $user.EmailAddress 19 | 20 | Write-Output "Blocking sign in for: $upn" 21 | 22 | Set-MsolUser -TenantId $tenantID -UserPrincipalName $upn -BlockCredential $true 23 | 24 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Enable-MailboxAuditing/delegated.ps1: -------------------------------------------------------------------------------- 1 | $ScriptBlock = {Get-Mailbox -ResultSize Unlimited | Set-Mailbox -AuditEnabled $true -AuditOwner MailboxLogin, HardDelete, SoftDelete, Update, Move -AuditDelegate SendOnBehalf, MoveToDeletedItems, Move -AuditAdmin Copy, MessageBind} 2 | 3 | # Establish a PowerShell session with Office 365. You'll be prompted for your Delegated Admin credentials 4 | 5 | $Cred = Get-Credential 6 | Connect-MsolService -Credential $Cred 7 | $customers = Get-MsolPartnerContract -All 8 | Write-Host "Found $($customers.Count) customers for this Partner." 9 | 10 | foreach ($customer in $customers) { 11 | 12 | $InitialDomain = Get-MsolDomain -TenantId $customer.TenantId | Where-Object {$_.IsInitial -eq $true} 13 | Write-Host "Enabling Mailbox Auditing for $($Customer.Name)" 14 | $DelegatedOrgURL = "https://ps.outlook.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name 15 | Invoke-Command -ConnectionUri $DelegatedOrgURL -Credential $Cred -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection -ScriptBlock $ScriptBlock -HideComputerName 16 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 GCITS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Block-ExternalMailForwarding/single.ps1: -------------------------------------------------------------------------------- 1 | Function Connect-EXOnline { 2 | $credentials = Get-Credential 3 | Write-Output "Getting the Exchange Online cmdlets" 4 | $session = New-PSSession -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` 5 | -ConfigurationName Microsoft.Exchange -Credential $credentials ` 6 | -Authentication Basic -AllowRedirection 7 | Import-PSSession $session 8 | } 9 | Connect-EXOnline 10 | 11 | $externalTransportRuleName = "Inbox Rules To External Block" 12 | $rejectMessageText = "To improve security, auto-forwarding rules to external addresses has been disabled. Please contact your Microsoft Partner if you'd like to set up an exception." 13 | 14 | $externalForwardRule = Get-TransportRule | Where-Object {$_.Identity -contains $externalTransportRuleName} 15 | 16 | if (!$externalForwardRule) { 17 | Write-Output "Client Rules To External Block not found, creating Rule" 18 | New-TransportRule -name "Client Rules To External Block" -Priority 1 -SentToScope NotInOrganization -FromScope InOrganization -MessageTypeMatches AutoForward -RejectMessageEnhancedStatusCode 5.7.1 -RejectMessageReasonText $rejectMessageText 19 | } -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Export-UserLicenceList/delegated.ps1: -------------------------------------------------------------------------------- 1 | #Establish a PowerShell session with Office 365. You'll be prompted for your Delegated Admin credentials 2 | Connect-MsolService 3 | $customers = Get-MsolPartnerContract -All 4 | Write-Host "Found $($customers.Count) customers for $((Get-MsolCompanyInformation).displayname)." -ForegroundColor DarkGreen 5 | $CSVpath = "C:\Temp\UserLicenseReport.csv" 6 | 7 | foreach ($customer in $customers) { 8 | Write-Host "Retrieving license info for $($customer.name)" -ForegroundColor Green 9 | $licensedUsers = Get-MsolUser -TenantId $customer.TenantId -All | Where-Object {$_.islicensed} 10 | 11 | foreach ($user in $licensedUsers) { 12 | Write-Host "$($user.displayname)" -ForegroundColor Yellow 13 | $licenses = $user.Licenses 14 | $licenseArray = $licenses | foreach-Object {$_.AccountSkuId} 15 | $licenseString = $licenseArray -join ", " 16 | Write-Host "$($user.displayname) has $licenseString" -ForegroundColor Blue 17 | $licensedSharedMailboxProperties = [pscustomobject][ordered]@{ 18 | CustomerName = $customer.Name 19 | DisplayName = $user.DisplayName 20 | Licenses = $licenseString 21 | TenantId = $customer.TenantId 22 | UserPrincipalName = $user.UserPrincipalName 23 | } 24 | $licensedSharedMailboxProperties | Export-CSV -Path $CSVpath -Append -NoTypeInformation 25 | } 26 | } -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Block-ExternalMailForwarding/delegated.ps1: -------------------------------------------------------------------------------- 1 | $credential = Get-Credential 2 | Connect-MsolService -Credential $credential 3 | $customers = Get-MsolPartnerContract -All 4 | $externalTransportRuleName = "Inbox Rules To External Block" 5 | $rejectMessageText = "To improve security, auto-forwarding rules to external addresses has been disabled. Please contact your Microsoft Partner if you'd like to set up an exception." 6 | 7 | Write-Output "Found $($customers.Count) customers for $((Get-MsolCompanyInformation).displayname)." 8 | 9 | foreach ($customer in $customers) { 10 | $InitialDomain = Get-MsolDomain -TenantId $customer.TenantId | Where-Object {$_.IsInitial -eq $true} 11 | 12 | Write-Output "Checking transport rule for $($Customer.Name)" 13 | $DelegatedOrgURL = "https://outlook.office365.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name 14 | $s = New-PSSession -ConnectionUri $DelegatedOrgURL -Credential $credential -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection 15 | Import-PSSession $s -CommandName Get-TransportRule, New-TransportRule, Set-TransportRule -AllowClobber 16 | 17 | $externalForwardRule = Get-TransportRule | Where-Object {$_.Identity -contains $externalTransportRuleName} 18 | 19 | if (!$externalForwardRule) { 20 | Write-Output "Client Rules To External Block not found, creating Rule" 21 | New-TransportRule -name "Client Rules To External Block" -Priority 1 -SentToScope NotInOrganization -FromScope InOrganization -MessageTypeMatches AutoForward -RejectMessageEnhancedStatusCode 5.7.1 -RejectMessageReasonText $rejectMessageText 22 | } 23 | Remove-PSSession $s 24 | } -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Enable-MailboxAuditing/auzreFunction.ps1: -------------------------------------------------------------------------------- 1 | Write-Output "PowerShell Timer trigger function executed at:$(get-date)"; 2 | 3 | $FunctionName = 'EnableMailboxAuditing' 4 | $ModuleName = 'MSOnline' 5 | $ModuleVersion = '1.1.166.0' 6 | $username = $Env:user 7 | $pw = $Env:password 8 | #import PS module 9 | $PSModulePath = "D:\home\site\wwwroot\$FunctionName\bin\$ModuleName\$ModuleVersion\$ModuleName.psd1" 10 | $res = "D:\home\site\wwwroot\$FunctionName\bin" 11 | 12 | Import-module $PSModulePath 13 | 14 | # Build Credentials 15 | $keypath = "D:\home\site\wwwroot\$FunctionName\bin\keys\PassEncryptKey.key" 16 | $secpassword = $pw | ConvertTo-SecureString -Key (Get-Content $keypath) 17 | $credential = New-Object System.Management.Automation.PSCredential ($username, $secpassword) 18 | 19 | # Connect to MSOnline 20 | 21 | Connect-MsolService -Credential $credential 22 | 23 | $ScriptBlock = {Get-Mailbox | Set-Mailbox -AuditEnabled $true -AuditOwner MailboxLogin, HardDelete, SoftDelete, Update, Move -AuditDelegate SendOnBehalf, MoveToDeletedItems, Move -AuditAdmin Copy, MessageBind} 24 | 25 | $customers = Get-MsolPartnerContract -All 26 | Write-Output "Found $($customers.Count) customers for this Partner." 27 | 28 | foreach ($customer in $customers) { 29 | 30 | $InitialDomain = Get-MsolDomain -TenantId $customer.TenantId | Where-Object {$_.IsInitial -eq $true} 31 | Write-Output "Enabling Mailbox Auditing for $($Customer.Name)" 32 | $DelegatedOrgURL = "https://ps.outlook.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name 33 | Invoke-Command -ConnectionUri $DelegatedOrgURL -Credential $credential -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection -ScriptBlock $ScriptBlock -HideComputerName 34 | } -------------------------------------------------------------------------------- /Office365/General/Enable-AuditLogging/scripts3.ps1: -------------------------------------------------------------------------------- 1 | <# This script will use the admin users created by the first script to enable the Unified Audit Log in each tenant. If enabling the Unified Audit Log is successful, it'll remove the created admin. If it's not successful, it'll keep the admin in place and add it to another CSV. You can retry these tenants by modifying the $Customers value to import the RemainingAdminsCsv in the next run. #> 2 | 3 | #------------------------------------------------------------- 4 | 5 | # Here are some things you can modify: 6 | 7 | # This is your partner admin user name that has delegated administration permission 8 | 9 | $UserName = "training@gcits.com" 10 | 11 | # This CSV contains a list of all remaining unsuccessful admins left unchanged by the second script. 12 | 13 | $RemainingAdmins = import-csv "C:\temp\RemainingAdmins.csv" 14 | 15 | # This CSV will contain a list of all admins removed by this script. 16 | 17 | $RemovedAdminsCsv = "C:\temp\RemovedAdmins.csv" 18 | 19 | #------------------------------------------------------------- 20 | 21 | $Cred = get-credential -Credential $UserName 22 | 23 | Connect-MsolService -Credential $cred 24 | 25 | ForEach ($Admin in $RemainingAdmins) { 26 | 27 | $tenantID = $Admin.Tenantid 28 | 29 | $upn = $Admin.UserPrincipalName 30 | 31 | Write-Output "Deleting user: $upn" 32 | 33 | Remove-MsolUser -UserPrincipalName $upn -TenantId $tenantID -Force 34 | 35 | 36 | $AdminProperties = @{ 37 | TenantId = $tenantID 38 | CompanyName = $Admin.CompanyName 39 | DefaultDomainName = $Admin.DefaultDomainName 40 | UserPrincipalName = $upn 41 | Action = "REMOVED" 42 | } 43 | 44 | $RemovedAdmins = @() 45 | $RemovedAdmins += New-Object psobject -Property $AdminProperties 46 | $RemovedAdmins | Select-Object TenantId, CompanyName, DefaultDomainName, UserPrincipalName, Action | Export-Csv -notypeinformation -Path $RemovedAdminsCsv -Append 47 | 48 | 49 | } -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Enable-IncreaseE3MailboxSizeTo100GB/single.ps1: -------------------------------------------------------------------------------- 1 | $credential = Get-Credential 2 | Connect-MsolService -Credential $credential 3 | 4 | $users = Get-MsolUser -All | where-object {$_.licenses.accountskuid -match "enterprisepack" -or ` 5 | $_.licenses.accountskuid -match "SPE_E3" -or $_.licenses.accountskuid -match "SPE_E5" ` 6 | -or $_.licenses.accountskuid -match "EXCHANGE_S_ENTERPRISE"} 7 | 8 | if ($users) { 9 | $Session = New-PSSession -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` 10 | -ConfigurationName Microsoft.Exchange -Credential $credential ` 11 | -Authentication Basic -AllowRedirection 12 | Import-PSSession $Session -CommandName Get-Mailbox, Set-Mailbox 13 | 14 | 15 | $reports = @() 16 | foreach ($user in $users) { 17 | $mailbox = get-mailbox $user.userprincipalname 18 | if ($mailbox.ProhibitSendReceiveQuota -match "50 GB") { 19 | Write-Host "$($mailbox.displayname) is only 50 GB, resizing..." -ForegroundColor Yellow 20 | Set-Mailbox $mailbox.PrimarySmtpAddress -ProhibitSendReceiveQuota 100GB -ProhibitSendQuota 99GB -IssueWarningQuota 98GB 21 | $mailboxCheck = get-mailbox $mailbox.PrimarySmtpAddress 22 | Write-Host "New mailbox maximum size is $($mailboxcheck.ProhibitSendReceiveQuota)" -ForegroundColor Green 23 | $reportHash = @{ 24 | DisplayName = $user.DisplayName 25 | PrimarySmtpAddress = $mailbox.PrimarySmtpAddress 26 | Licenses = $user.licenses.accountskuid -join ", " 27 | ProhibitSendReceiveQuota = $mailboxCheck.ProhibitSendReceiveQuota 28 | 29 | } 30 | $reportObject = New-Object psobject -Property $reportHash 31 | $reports += $reportObject 32 | } 33 | } 34 | 35 | $reports | export-csv C:\temp\MailboxResizeReport.csv -NoTypeInformation -Append 36 | Remove-PSSession $Session 37 | } -------------------------------------------------------------------------------- /ITGlue/HaveIBeenPwnedPasswordCheck/single.ps1: -------------------------------------------------------------------------------- 1 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 2 | 3 | $key = "ENTERAPIKEYHERE" 4 | $ITGbaseURI = "https://api.itglue.com" 5 | 6 | $headers = @{ 7 | "x-api-key" = $key 8 | } 9 | 10 | Function Get-StringHash([String] $String, $HashName = "MD5") { 11 | $StringBuilder = New-Object System.Text.StringBuilder 12 | [System.Security.Cryptography.HashAlgorithm]::Create($HashName).ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String))| % { 13 | [Void]$StringBuilder.Append($_.ToString("x2")) 14 | } 15 | $StringBuilder.ToString() 16 | } 17 | 18 | function Get-ITGlueItem($Resource) { 19 | $array = @() 20 | 21 | $body = Invoke-RestMethod -Method get -Uri "$ITGbaseUri/$Resource" -Headers $headers -ContentType application/vnd.api+json 22 | $array += $body.data 23 | Write-Host "Retrieved $($array.Count) items" 24 | 25 | if ($body.links.next) { 26 | do { 27 | $body = Invoke-RestMethod -Method get -Uri $body.links.next -Headers $headers -ContentType application/vnd.api+json 28 | $array += $body.data 29 | Write-Host "Retrieved $($array.Count) items" 30 | } while ($body.links.next) 31 | } 32 | return $array 33 | } 34 | 35 | $passwords = Get-ITGlueItem -Resource passwords 36 | 37 | foreach ($password in $passwords) { 38 | $details = Get-ITGlueItem -Resource passwords/$($password.id) 39 | $hash = Get-StringHash -String $details.attributes.password -HashName SHA1 40 | $first5 = $hash.Substring(0, 5) 41 | $remaining = $hash.Substring(5) 42 | $result = Invoke-Restmethod -Uri "https://api.pwnedpasswords.com/range/$first5" 43 | $result = $result -split "`n" 44 | $match = $result | Where-Object {$_ -match $remaining} 45 | if ($match) { 46 | $FoundCount = ($match -split ":")[1] 47 | Write-Host $FoundCount -ForegroundColor Red 48 | Write-Host "Found $($details.attributes.'organization-name') - $($details.attributes.name)`n" -ForegroundColor Yellow 49 | $password.attributes | Add-Member FoundCount $FoundCount -Force 50 | $password.attributes | export-csv C:\temp\pwnedpasswords.csv -NoTypeInformation -Append 51 | } 52 | } -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Export-UserExternalMailForwarding/single.ps1: -------------------------------------------------------------------------------- 1 | Function Connect-EXOnline { 2 | $credentials = Get-Credential 3 | Write-Output "Getting the Exchange Online cmdlets" 4 | $Session = New-PSSession -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` 5 | -ConfigurationName Microsoft.Exchange -Credential $credentials ` 6 | -Authentication Basic -AllowRedirection 7 | Import-PSSession $Session 8 | } 9 | 10 | Connect-EXOnline 11 | $domains = Get-AcceptedDomain 12 | $mailboxes = Get-Mailbox -ResultSize Unlimited 13 | 14 | foreach ($mailbox in $mailboxes) { 15 | 16 | $forwardingRules = $null 17 | Write-Host "Checking rules for $($mailbox.displayname) - $($mailbox.primarysmtpaddress)" -foregroundColor Green 18 | $rules = get-inboxrule -Mailbox $mailbox.primarysmtpaddress 19 | 20 | $forwardingRules = $rules | Where-Object {$_.forwardto -or $_.forwardasattachmentto} 21 | 22 | foreach ($rule in $forwardingRules) { 23 | $recipients = @() 24 | $recipients = $rule.ForwardTo | Where-Object {$_ -match "SMTP"} 25 | $recipients += $rule.ForwardAsAttachmentTo | Where-Object {$_ -match "SMTP"} 26 | 27 | $externalRecipients = @() 28 | 29 | foreach ($recipient in $recipients) { 30 | $email = ($recipient -split "SMTP:")[1].Trim("]") 31 | $domain = ($email -split "@")[1] 32 | 33 | if ($domains.DomainName -notcontains $domain) { 34 | $externalRecipients += $email 35 | } 36 | } 37 | 38 | if ($externalRecipients) { 39 | $extRecString = $externalRecipients -join ", " 40 | Write-Host "$($rule.Name) forwards to $extRecString" -ForegroundColor Yellow 41 | 42 | $ruleHash = $null 43 | $ruleHash = [ordered]@{ 44 | PrimarySmtpAddress = $mailbox.PrimarySmtpAddress 45 | DisplayName = $mailbox.DisplayName 46 | RuleId = $rule.Identity 47 | RuleName = $rule.Name 48 | RuleDescription = $rule.Description 49 | ExternalRecipients = $extRecString 50 | } 51 | $ruleObject = New-Object PSObject -Property $ruleHash 52 | $ruleObject | Export-Csv C:\temp\externalrules.csv -NoTypeInformation -Append 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /Office365/General/Export-AdministrativeUsers/Administrators.ps1: -------------------------------------------------------------------------------- 1 | cls 2 | 3 | # This is the username of an Office 365 account with delegated admin permissions 4 | 5 | $UserName = "training@gcits.com" 6 | 7 | $Cred = get-credential -Credential $UserName 8 | 9 | #This script is looking for unlicensed Company Administrators. Though you can update the role here to look for another role type. 10 | 11 | $RoleName = "Company Administrator" 12 | 13 | Connect-MSOLService -Credential $Cred 14 | 15 | Import-Module MSOnline 16 | 17 | $Customers = Get-MsolPartnerContract -All 18 | 19 | $msolUserResults = @() 20 | 21 | # This is the path of the exported CSV. You'll need to create a C:\temp folder. You can change this, though you'll need to update the next script with the new path. 22 | 23 | $msolUserCsv = "C:\temp\AdminUserList.csv" 24 | 25 | 26 | ForEach ($Customer in $Customers) { 27 | 28 | Write-Host "----------------------------------------------------------" 29 | Write-Host "Getting Unlicensed Admins for $($Customer.Name)" 30 | Write-Host " " 31 | 32 | 33 | $CompanyAdminRole = Get-MsolRole | Where-Object {$_.Name -match $RoleName} 34 | $RoleID = $CompanyAdminRole.ObjectID 35 | $Admins = Get-MsolRoleMember -TenantId $Customer.TenantId -RoleObjectId $RoleID 36 | 37 | foreach ($Admin in $Admins) { 38 | 39 | if ($Admin.EmailAddress -ne $null) { 40 | 41 | $MsolUserDetails = Get-MsolUser -UserPrincipalName $Admin.EmailAddress -TenantId $Customer.TenantId 42 | 43 | $LicenseStatus = $MsolUserDetails.IsLicensed 44 | $userProperties = @{ 45 | 46 | TenantId = $Customer.TenantID 47 | CompanyName = $Customer.Name 48 | PrimaryDomain = $Customer.DefaultDomainName 49 | DisplayName = $Admin.DisplayName 50 | EmailAddress = $Admin.EmailAddress 51 | IsLicensed = $LicenseStatus 52 | BlockCredential = $MsolUserDetails.BlockCredential 53 | } 54 | 55 | Write-Host "$($Admin.DisplayName) from $($Customer.Name) is an unlicensed Company Admin" 56 | 57 | $msolUserResults += New-Object psobject -Property $userProperties 58 | 59 | } 60 | } 61 | 62 | Write-Host " " 63 | 64 | } 65 | 66 | $msolUserResults | Select-Object TenantId, CompanyName, PrimaryDomain, DisplayName, EmailAddress, IsLicensed, BlockCredential | Export-Csv -notypeinformation -Path $msolUserCsv 67 | 68 | Write-Host "Export Complete" -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Warning-UserImpersonation/single.ps1: -------------------------------------------------------------------------------- 1 | $ruleName = "External Senders with matching Display Names" 2 | $ruleHtml = "

This message was sent from outside the company by someone with a display name matching a user in your organisation. Please do not click links or open attachments unless you recognise the source of this email and know the content is safe.

" 3 | 4 | $credentials = Get-Credential 5 | 6 | Write-Host "Getting the Exchange Online cmdlets" -ForegroundColor Yellow 7 | $Session = New-PSSession -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` 8 | -ConfigurationName Microsoft.Exchange -Credential $credentials ` 9 | -Authentication Basic -AllowRedirection 10 | Import-PSSession $Session -AllowClobber 11 | 12 | $rule = Get-TransportRule | Where-Object {$_.Identity -contains $ruleName} 13 | $displayNames = (Get-Mailbox -ResultSize Unlimited).DisplayName 14 | 15 | if (!$rule) { 16 | Write-Host "Rule not found, creating rule" -ForegroundColor Green 17 | New-TransportRule -Name $ruleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" ` 18 | -HeaderMatchesMessageHeader From -HeaderMatchesPatterns $displayNames -ApplyHtmlDisclaimerText $ruleHtml 19 | } 20 | else { 21 | Write-Host "Rule found, updating rule" -ForegroundColor Green 22 | Set-TransportRule -Identity $ruleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" ` 23 | -HeaderMatchesMessageHeader From -HeaderMatchesPatterns $displayNames -ApplyHtmlDisclaimerText $ruleHtml 24 | } 25 | Remove-PSSession $Session -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Block-ExternalMailForwarding/azureFunction.ps1: -------------------------------------------------------------------------------- 1 | Write-Output "PowerShell Timer trigger function executed at:$(get-date)"; 2 | 3 | $FunctionName = 'BlockExternalForwardingFromInboxRules' 4 | $ModuleName = 'MSOnline' 5 | $ModuleVersion = '1.1.166.0' 6 | $username = $Env:user 7 | $pw = $Env:password 8 | #import PS module 9 | $PSModulePath = "D:\home\site\wwwroot\$FunctionName\bin\$ModuleName\$ModuleVersion\$ModuleName.psd1" 10 | $res = "D:\home\site\wwwroot\$FunctionName\bin" 11 | 12 | Import-module $PSModulePath 13 | 14 | # Build Credentials 15 | $keypath = "D:\home\site\wwwroot\$FunctionName\bin\keys\PassEncryptKey.key" 16 | $secpassword = $pw | ConvertTo-SecureString -Key (Get-Content $keypath) 17 | $credential = New-Object System.Management.Automation.PSCredential ($username, $secpassword) 18 | 19 | # Connect to MSOnline 20 | Connect-MsolService -Credential $credential 21 | $customers = @() 22 | 23 | Write-Output "Found $($customers.Count) customers for $((Get-MsolCompanyInformation).displayname)." 24 | 25 | $externalTransportRuleName = 'Inbox Rules To External Block' 26 | $rejectMessageText = "To improve security, auto-forwarding rules to external addresses has been disabled. Please contact your Microsoft Partner if you'd like to set up an exception." 27 | 28 | foreach ($customer in $customers) { 29 | $InitialDomain = Get-MsolDomain -TenantId $customer.TenantId | Where-Object {$_.IsInitial -eq $true} 30 | 31 | Write-Output "Checking transport rule for $($Customer.Name)" 32 | $DelegatedOrgURL = "https://outlook.office365.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name 33 | $s = New-PSSession -ConnectionUri $DelegatedOrgURL -Credential $credential -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection 34 | Import-PSSession $s -CommandName Get-Mailbox, Get-TransportRule, New-TransportRule, Set-TransportRule -AllowClobber 35 | 36 | $externalForwardRule = Get-TransportRule | Where-Object {$_.Identity -contains $externalTransportRuleName} 37 | 38 | if (!$externalForwardRule) { 39 | Write-Output "Client Rules To External Block not found, creating Rule" 40 | New-TransportRule -name 'Client Rules To External Block' -Priority 1 -SentToScope NotInOrganization -FromScope InOrganization -MessageTypeMatches AutoForward -RejectMessageEnhancedStatusCode 5.7.1 -RejectMessageReasonText $rejectMessageText 41 | } 42 | 43 | Remove-PSSession $s 44 | } -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Enable-IncreaseE3MailboxSizeTo100GB/delegated.ps1: -------------------------------------------------------------------------------- 1 | $credential = Get-Credential 2 | Connect-MsolService -Credential $credential 3 | 4 | $customers = Get-MsolPartnerContract 5 | 6 | foreach ($customer in $customers) { 7 | Write-Host "Checking Mailbox Sizes for $($customer.name)" -ForegroundColor Green 8 | $users = $null 9 | $users = Get-MsolUser -TenantId $customer.tenantid | where-object {$_.licenses.accountskuid -match "enterprisepack" -or ` 10 | $_.licenses.accountskuid -match "SPE_E3" -or $_.licenses.accountskuid -match "SPE_E5" ` 11 | -or $_.licenses.accountskuid -match "EXCHANGE_S_ENTERPRISE"} 12 | 13 | if ($users) { 14 | $InitialDomain = Get-MsolDomain -TenantId $customer.tenantid | Where-Object {$_.IsInitial -eq $true} 15 | $DelegatedOrgURL = "https://outlook.office365.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name 16 | $EXODS = New-PSSession -ConnectionUri $DelegatedOrgURL -Credential $credential -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection 17 | Import-PSSession $EXODS -CommandName Get-Mailbox, Set-Mailbox -AllowClobber 18 | 19 | $reports = @() 20 | foreach ($user in $users) { 21 | $mailbox = get-mailbox $user.userprincipalname 22 | if ($mailbox.ProhibitSendReceiveQuota -match "50 GB") { 23 | Write-Host "$($mailbox.displayname) is only 50 GB, resizing..." -ForegroundColor Yellow 24 | Set-Mailbox $mailbox.PrimarySmtpAddress -ProhibitSendReceiveQuota 100GB -ProhibitSendQuota 99GB -IssueWarningQuota 98GB 25 | $mailboxCheck = get-mailbox $mailbox.PrimarySmtpAddress 26 | Write-Host "New mailbox maximum size is $($mailboxcheck.ProhibitSendReceiveQuota)" -ForegroundColor Green 27 | $reportHash = @{ 28 | CustomerName = $customer.Name 29 | TenantId = $customer.TenantId 30 | DisplayName = $user.DisplayName 31 | PrimarySmtpAddress = $mailbox.PrimarySmtpAddress 32 | Licenses = $user.licenses.accountskuid -join ", " 33 | ProhibitSendReceiveQuota = $mailboxCheck.ProhibitSendReceiveQuota 34 | 35 | } 36 | $reportObject = New-Object psobject -Property $reportHash 37 | $reports += $reportObject 38 | } 39 | } 40 | $reports | export-csv C:\temp\MailboxResizeReport.csv -NoTypeInformation -Append 41 | Remove-PSSession $EXODS 42 | } 43 | } -------------------------------------------------------------------------------- /Office365/General/Export-AdministrativeUsers/unlicencedAdministrators.ps1: -------------------------------------------------------------------------------- 1 | cls 2 | 3 | # This is the username of an Office 365 account with delegated admin permissions 4 | 5 | $UserName = "training@gcits.com" 6 | 7 | $Cred = get-credential -Credential $UserName 8 | 9 | #This script is looking for unlicensed Company Administrators. Though you can update the role here to look for another role type. 10 | 11 | $RoleName = "Company Administrator" 12 | 13 | Connect-MSOLService -Credential $Cred 14 | 15 | Import-Module MSOnline 16 | 17 | $Customers = Get-MsolPartnerContract -All 18 | 19 | $msolUserResults = @() 20 | 21 | # This is the path of the exported CSV. You'll need to create a C:\temp folder. You can change this, though you'll need to update the next script with the new path. 22 | 23 | $msolUserCsv = "C:\temp\AdminUserList.csv" 24 | 25 | 26 | ForEach ($Customer in $Customers) { 27 | 28 | Write-Host "----------------------------------------------------------" 29 | Write-Host "Getting Unlicensed Admins for $($Customer.Name)" 30 | Write-Host " " 31 | 32 | 33 | $CompanyAdminRole = Get-MsolRole | Where-Object {$_.Name -match $RoleName} 34 | $RoleID = $CompanyAdminRole.ObjectID 35 | $Admins = Get-MsolRoleMember -TenantId $Customer.TenantId -RoleObjectId $RoleID 36 | 37 | foreach ($Admin in $Admins) { 38 | 39 | if ($Admin.EmailAddress -ne $null) { 40 | 41 | $MsolUserDetails = Get-MsolUser -UserPrincipalName $Admin.EmailAddress -TenantId $Customer.TenantId 42 | 43 | if (!$Admin.IsLicensed) { 44 | 45 | $LicenseStatus = $MsolUserDetails.IsLicensed 46 | $userProperties = @{ 47 | 48 | TenantId = $Customer.TenantID 49 | CompanyName = $Customer.Name 50 | PrimaryDomain = $Customer.DefaultDomainName 51 | DisplayName = $Admin.DisplayName 52 | EmailAddress = $Admin.EmailAddress 53 | IsLicensed = $LicenseStatus 54 | BlockCredential = $MsolUserDetails.BlockCredential 55 | } 56 | 57 | Write-Host "$($Admin.DisplayName) from $($Customer.Name) is an unlicensed Company Admin" 58 | 59 | $msolUserResults += New-Object psobject -Property $userProperties 60 | } 61 | } 62 | } 63 | 64 | Write-Host " " 65 | 66 | } 67 | 68 | $msolUserResults | Select-Object TenantId, CompanyName, PrimaryDomain, DisplayName, EmailAddress, IsLicensed, BlockCredential | Export-Csv -notypeinformation -Path $msolUserCsv 69 | 70 | Write-Host "Export Complete" -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Export-UserExternalMailForwarding/delegated.ps1: -------------------------------------------------------------------------------- 1 | $credential = Get-Credential 2 | Connect-MsolService -Credential $credential 3 | $customers = Get-msolpartnercontract 4 | foreach ($customer in $customers) { 5 | 6 | $InitialDomain = Get-MsolDomain -TenantId $customer.TenantId | Where-Object {$_.IsInitial -eq $true} 7 | 8 | Write-Host "Checking $($customer.Name)" 9 | $DelegatedOrgURL = "https://outlook.office365.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name 10 | $s = New-PSSession -ConnectionUri $DelegatedOrgURL -Credential $credential -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection 11 | Import-PSSession $s -CommandName Get-Mailbox, Get-InboxRule, Get-AcceptedDomain -AllowClobber 12 | $mailboxes = $null 13 | $mailboxes = Get-Mailbox -ResultSize Unlimited 14 | $domains = Get-AcceptedDomain 15 | 16 | foreach ($mailbox in $mailboxes) { 17 | 18 | $forwardingRules = $null 19 | 20 | Write-Host "Checking rules for $($mailbox.displayname) - $($mailbox.primarysmtpaddress)" 21 | $rules = get-inboxrule -Mailbox $mailbox.primarysmtpaddress 22 | $forwardingRules = $rules | Where-Object {$_.forwardto -or $_.forwardasattachmentto} 23 | 24 | foreach ($rule in $forwardingRules) { 25 | $recipients = @() 26 | $recipients = $rule.ForwardTo | Where-Object {$_ -match "SMTP"} 27 | $recipients += $rule.ForwardAsAttachmentTo | Where-Object {$_ -match "SMTP"} 28 | $externalRecipients = @() 29 | 30 | foreach ($recipient in $recipients) { 31 | $email = ($recipient -split "SMTP:")[1].Trim("]") 32 | $domain = ($email -split "@")[1] 33 | 34 | if ($domains.DomainName -notcontains $domain) { 35 | $externalRecipients += $email 36 | } 37 | } 38 | 39 | if ($externalRecipients) { 40 | $extRecString = $externalRecipients -join ", " 41 | Write-Host "$($rule.Name) forwards to $extRecString" -ForegroundColor Yellow 42 | 43 | $ruleHash = $null 44 | $ruleHash = [ordered]@{ 45 | Customer = $customer.Name 46 | TenantId = $customer.TenantId 47 | PrimarySmtpAddress = $mailbox.PrimarySmtpAddress 48 | DisplayName = $mailbox.DisplayName 49 | RuleId = $rule.Identity 50 | RuleName = $rule.Name 51 | RuleDescription = $rule.Description 52 | ExternalRecipients = $extRecString 53 | } 54 | $ruleObject = New-Object PSObject -Property $ruleHash 55 | $ruleObject | Export-Csv C:\temp\customerExternalRules.csv -NoTypeInformation -Append 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Warning-UserImpersonation/azureFunctionSingle.ps1: -------------------------------------------------------------------------------- 1 | Write-Output "PowerShell Timer trigger function executed at:$(get-date)"; 2 | 3 | $FunctionName = 'ExchangeTransportExtWarning' 4 | $username = $Env:user 5 | $pw = $Env:password 6 | 7 | # Build Credentials 8 | $keypath = "D:\home\site\wwwroot\$FunctionName\bin\keys\PassEncryptKey.key" 9 | $secpassword = $pw | ConvertTo-SecureString -Key (Get-Content $keypath) 10 | $credential = New-Object System.Management.Automation.PSCredential ($username, $secpassword) 11 | 12 | $ruleName = "External Senders with matching Display Names" 13 | $ruleHtml = "

This message was sent from outside the company by someone with a display name matching a user in your organisation. Please do not click links or open attachments unless you recognise the source of this email and know the content is safe.

" 14 | 15 | Write-Output "Getting the Exchange Online cmdlets" 16 | 17 | $Session = New-PSSession -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` 18 | -ConfigurationName Microsoft.Exchange -Credential $credential ` 19 | -Authentication Basic -AllowRedirection 20 | Import-PSSession $Session -AllowClobber 21 | 22 | 23 | $rule = Get-TransportRule | Where-Object {$_.Identity -contains $ruleName} 24 | $displayNames = (Get-Mailbox -ResultSize Unlimited).DisplayName 25 | 26 | if (!$rule) { 27 | Write-Output "Rule not found, creating rule" 28 | New-TransportRule -Name $ruleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" ` 29 | -HeaderMatchesMessageHeader From -HeaderMatchesPatterns $displayNames -ApplyHtmlDisclaimerText $ruleHtml 30 | } 31 | else { 32 | Write-Output "Rule found, updating rule" 33 | Set-TransportRule -Identity $ruleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" ` 34 | -HeaderMatchesMessageHeader From -HeaderMatchesPatterns $displayNames -ApplyHtmlDisclaimerText $ruleHtml 35 | } 36 | 37 | Remove-PSSession $Session -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Export-UserLoginLocation/single.ps1: -------------------------------------------------------------------------------- 1 | Function Connect-EXOnline { 2 | $credentials = Get-Credential -Credential $credential 3 | Write-Output "Getting the Exchange Online cmdlets" 4 | 5 | $Session = New-PSSession -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` 6 | -ConfigurationName Microsoft.Exchange -Credential $credentials ` 7 | -Authentication Basic -AllowRedirection 8 | Import-PSSession $Session -AllowClobber 9 | } 10 | $credential = Get-Credential 11 | Connect-EXOnline 12 | 13 | 14 | $startDate = (Get-Date).AddDays(-30) 15 | $endDate = (Get-Date) 16 | $Logs = @() 17 | Write-Host "Retrieving logs" -ForegroundColor Blue 18 | do { 19 | $logs += Search-unifiedAuditLog -SessionCommand ReturnLargeSet -SessionId "UALSearch" -ResultSize 5000 -StartDate $startDate -EndDate $endDate -Operations UserLoggedIn #-SessionId "$($customer.name)" 20 | Write-Host "Retrieved $($logs.count) logs" -ForegroundColor Yellow 21 | }while ($Logs.count % 5000 -eq 0 -and $logs.count -ne 0) 22 | Write-Host "Finished Retrieving logs" -ForegroundColor Green 23 | 24 | $userIds = $logs.userIds | Sort-Object -Unique 25 | 26 | foreach ($userId in $userIds) { 27 | 28 | $ips = @() 29 | Write-Host "Getting logon IPs for $userId" 30 | $searchResult = ($logs | Where-Object {$_.userIds -contains $userId}).auditdata | ConvertFrom-Json -ErrorAction SilentlyContinue 31 | Write-Host "$userId has $($searchResult.count) logs" -ForegroundColor Green 32 | 33 | $ips = $searchResult.clientip | Sort-Object -Unique 34 | Write-Host "Found $($ips.count) unique IP addresses for $userId" 35 | foreach ($ip in $ips) { 36 | Write-Host "Checking $ip" -ForegroundColor Yellow 37 | $mergedObject = @{} 38 | $singleResult = $searchResult | Where-Object {$_.clientip -contains $ip} | Select-Object -First 1 39 | Start-sleep -m 400 40 | $ipresult = Invoke-restmethod -method get -uri http://ip-api.com/json/$ip 41 | $UserAgent = $singleResult.extendedproperties.value[0] 42 | Write-Host "Country: $($ipResult.country) UserAgent: $UserAgent" 43 | $singleResultProperties = $singleResult | Get-Member -MemberType NoteProperty 44 | foreach ($property in $singleResultProperties) { 45 | if ($property.Definition -match "object") { 46 | $string = $singleResult.($property.Name) | ConvertTo-Json -Depth 10 47 | $mergedObject | Add-Member -Name $property.Name -Value $string -MemberType NoteProperty 48 | } 49 | else {$mergedObject | Add-Member -Name $property.Name -Value $singleResult.($property.Name) -MemberType NoteProperty} 50 | } 51 | $property = $null 52 | $ipProperties = $ipresult | get-member -MemberType NoteProperty 53 | 54 | foreach ($property in $ipProperties) { 55 | $mergedObject | Add-Member -Name $property.Name -Value $ipresult.($property.Name) -MemberType NoteProperty 56 | } 57 | $mergedObject | Select-Object UserId, Operation, CreationTime, @{Name = "UserAgent"; Expression = {$UserAgent}}, Query, ISP, City, RegionName, Country | export-csv C:\temp\UserLocationDataGCITS.csv -Append -NoTypeInformation 58 | } 59 | } -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Warning-UserImpersonation/delegated.ps1: -------------------------------------------------------------------------------- 1 | $ruleName = "Warn on external senders with matching display names" 2 | $ruleHtml = "

This message was sent from outside the company by someone with a display name matching a user in your organisation. Please do not click links or open attachments unless you recognise the source of this email and know the content is safe.

" 3 | 4 | # Establish a PowerShell session with Office 365. You'll be prompted for your Delegated Admin credentials 5 | $Cred = Get-Credential 6 | Connect-MsolService -Credential $Cred 7 | $customers = Get-MsolPartnerContract | Where-Object {$_.name -match "Customer1" -or $_.name -match "Customer2" -or $_.name -match "Customer3"} 8 | Write-Host "Found $($customers.Count) customers for $((Get-MsolCompanyInformation).displayname)." 9 | 10 | foreach ($customer in $customers) { 11 | $InitialDomain = Get-MsolDomain -TenantId $customer.TenantId | Where-Object {$_.IsInitial -eq $true} 12 | 13 | Write-Host "Checking transport rule for $($Customer.Name)" -ForegroundColor Green 14 | $DelegatedOrgURL = "https://outlook.office365.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name 15 | $s = New-PSSession -ConnectionUri $DelegatedOrgURL -Credential $Cred -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection 16 | Import-PSSession $s -CommandName Get-Mailbox, Get-TransportRule, New-TransportRule, Set-TransportRule -AllowClobber 17 | 18 | $rule = Get-TransportRule | Where-Object {$_.Identity -contains $ruleName} 19 | $displayNames = (Get-Mailbox -ResultSize Unlimited).DisplayName 20 | 21 | if (!$rule) { 22 | Write-Host "Rule not found, creating Rule" -ForegroundColor Yellow 23 | New-TransportRule -Name $ruleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" ` 24 | -HeaderMatchesMessageHeader From -HeaderMatchesPatterns $displayNames -ApplyHtmlDisclaimerText $ruleHtml 25 | } 26 | else { 27 | Write-Host "Rule found, updating Rule" -ForegroundColor Yellow 28 | Set-TransportRule -Identity $ruleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" ` 29 | -HeaderMatchesMessageHeader From -HeaderMatchesPatterns $displayNames -ApplyHtmlDisclaimerText $ruleHtml 30 | } 31 | 32 | Remove-PSSession $s 33 | } -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Export-UserLicenceList/README.md: -------------------------------------------------------------------------------- 1 | [Source](https://gcits.com/knowledge-base/export-list-office-365-users-licenses-customer-tenants-delegated-administration/ "Permalink to Export a list of Office 365 users and their licenses in all customer tenants with delegated administration") 2 | 3 | # Export a list of Office 365 users and their licenses in all customer tenants with delegated administration 4 | 5 | Here's a script that reports on all licensed Office 365 users in all of your customer tenants, and exports their details and their license info to a CSV file. 6 | 7 | It uses your Office 365 delegated admin credentials to retrieve all of your customers.![Retrieve All Office 365 Customers][1] 8 | 9 | Then it pulls user and license info from each customer.![Retrieve License Info From Office 365 Customers][2] 10 | 11 | It appends this information to a CSV as it runs.![Office 365 User Info As CSV][3] 12 | 13 | ## How to export all customers' Office 365 users and license details to CSV 14 | 15 | 1. Copy and paste the code at the bottom of this page into Visual Studio Code. 16 | 2. Save it as a PowerShell (ps1) file. Install the PowerShell extension if prompted. 17 | 3. Press F5 to run the script. 18 | 4. Enter the credentials of an Office 365 Delegated Admin 19 | 5. Wait for it to complete 20 | 6. See a list of all users and their license info at C:tempUserLicenseReport.csv. 21 | 22 | ## Complete script to export all Office 365 customers user and license info to a CSV via PowerShell 23 | 24 | ```powershell 25 | #Establish a PowerShell session with Office 365. You'll be prompted for your Delegated Admin credentials 26 | Connect-MsolService 27 | $customers = Get-MsolPartnerContract -All 28 | Write-Host "Found $($customers.Count) customers for $((Get-MsolCompanyInformation).displayname)." -ForegroundColor DarkGreen 29 | $CSVpath = "C:TempUserLicenseReport.csv" 30 | 31 | foreach ($customer in $customers) { 32 | Write-Host "Retrieving license info for $($customer.name)" -ForegroundColor Green 33 | $licensedUsers = Get-MsolUser -TenantId $customer.TenantId -All | Where-Object {$_.islicensed} 34 | 35 | foreach ($user in $licensedUsers) { 36 | Write-Host "$($user.displayname)" -ForegroundColor Yellow 37 | $licenses = $user.Licenses 38 | $licenseArray = $licenses | foreach-Object {$_.AccountSkuId} 39 | $licenseString = $licenseArray -join ", " 40 | Write-Host "$($user.displayname) has $licenseString" -ForegroundColor Blue 41 | $licensedSharedMailboxProperties = [pscustomobject][ordered]@{ 42 | CustomerName = $customer.Name 43 | DisplayName = $user.DisplayName 44 | Licenses = $licenseString 45 | TenantId = $customer.TenantId 46 | UserPrincipalName = $user.UserPrincipalName 47 | } 48 | $licensedSharedMailboxProperties | Export-CSV -Path $CSVpath -Append -NoTypeInformation 49 | } 50 | } 51 | ``` 52 | 53 | ### About The Author 54 | 55 | ![Elliot Munro][4] 56 | 57 | #### [ Elliot Munro ][5] 58 | 59 | Elliot Munro is an Office 365 MCSA from the Gold Coast, Australia supporting hundreds of small businesses with GCITS. If you have an Office 365 or Azure issue that you'd like us to take a look at (or have a request for a useful script) send Elliot an email at [elliot@gcits.com][6] 60 | 61 | [1]: https://gcits.com/wp-content/uploads/RetrieveAllCustomers.png 62 | [2]: https://gcits.com/wp-content/uploads/RetrieveLicenseInfoFromCustomers.png 63 | [3]: https://gcits.com/wp-content/uploads/UserInfoAsCSV.png 64 | [4]: https://gcits.com/wp-content/uploads/AAEAAQAAAAAAAA2QAAAAJDNlN2NmM2Y4LTU5YWYtNGRiNC1hMmI2LTBhMzdhZDVmNWUzNA-80x80.jpg 65 | [5]: https://gcits.com/author/elliotmunro/ 66 | [6]: mailto:elliot%40gcits.com 67 | -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Export-UserLoginLocation/delegated.ps1: -------------------------------------------------------------------------------- 1 | $credential = Get-Credential 2 | Connect-MsolService -Credential $credential 3 | 4 | $customers = Get-msolpartnercontract -All 5 | foreach ($customer in $customers) { 6 | $company = Get-MsolCompanyInformation -TenantId $customer.TenantId 7 | $InitialDomain = Get-MsolDomain -TenantId $customer.TenantId | Where-Object {$_.IsInitial -eq $true} 8 | Write-Host "Getting logon location details for $($customer.Name)" -ForegroundColor Green 9 | $DelegatedOrgURL = "https://outlook.office365.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name 10 | $s = New-PSSession -ConnectionUri $DelegatedOrgURL -Credential $credential -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection 11 | Import-PSSession $s -CommandName Search-UnifiedAuditLog -AllowClobber 12 | 13 | $startDate = (Get-Date).AddDays(-30) 14 | $endDate = (Get-Date) 15 | $Logs = @() 16 | Write-Host "Retrieving logs for $($customer.name)" -ForegroundColor Blue 17 | do { 18 | $logs += Search-unifiedAuditLog -SessionCommand ReturnLargeSet -SessionId $customer.name -ResultSize 5000 -StartDate $startDate -EndDate $endDate -Operations UserLoggedIn #-SessionId "$($customer.name)" 19 | Write-Host "Retrieved $($logs.count) logs" -ForegroundColor Yellow 20 | }while ($Logs.count % 5000 -eq 0 -and $logs.count -ne 0) 21 | Write-Host "Finished Retrieving logs" -ForegroundColor Green 22 | 23 | $userIds = $logs.userIds | Sort-Object -Unique 24 | 25 | foreach ($userId in $userIds) { 26 | 27 | $ips = @() 28 | Write-Host "Getting logon IPs for $userId" 29 | $searchResult = ($logs | Where-Object {$_.userIds -contains $userId}).auditdata | ConvertFrom-Json -ErrorAction SilentlyContinue 30 | Write-Host "$userId has $($searchResult.count) logs" -ForegroundColor Green 31 | $ips = $searchResult.clientip | Sort-Object -Unique 32 | Write-Host "Found $($ips.count) unique IP addresses for $userId" 33 | foreach ($ip in $ips) { 34 | Write-Host "Checking $ip" -ForegroundColor Yellow 35 | $mergedObject = @{} 36 | $singleResult = $searchResult | Where-Object {$_.clientip -contains $ip} | Select-Object -First 1 37 | Start-sleep -m 400 38 | $ipresult = Invoke-restmethod -method get -uri http://ip-api.com/json/$ip 39 | $UserAgent = $singleResult.extendedproperties.value[0] 40 | Write-Host "Country: $($ipResult.country) UserAgent: $UserAgent" 41 | $singleResultProperties = $singleResult | Get-Member -MemberType NoteProperty 42 | foreach ($property in $singleResultProperties) { 43 | if ($property.Definition -match "object") { 44 | $string = $singleResult.($property.Name) | ConvertTo-Json -Depth 10 45 | $mergedObject | Add-Member -Name $property.Name -Value $string -MemberType NoteProperty 46 | } 47 | else {$mergedObject | Add-Member -Name $property.Name -Value $singleResult.($property.Name) -MemberType NoteProperty} 48 | } 49 | $property = $null 50 | $ipProperties = $ipresult | get-member -MemberType NoteProperty 51 | 52 | foreach ($property in $ipProperties) { 53 | $mergedObject | Add-Member -Name $property.Name -Value $ipresult.($property.Name) -MemberType NoteProperty 54 | } 55 | $mergedObject | Add-Member Company $company.displayname 56 | $mergedObject | Add-Member tenantID $customer.tenantID 57 | $mergedObject | Select-Object Company, tenantID, UserId, Operation, CreationTime, @{Name = "UserAgent"; Expression = {$UserAgent}}, Query, ISP, City, RegionName, Country | export-csv C:\temp\UserLocationData.csv -Append -NoTypeInformation 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Warning-UserImpersonation/azureFunctionDelegated.ps1: -------------------------------------------------------------------------------- 1 | Write-Output "PowerShell Timer trigger function executed at:$(get-date)"; 2 | 3 | $FunctionName = 'ExchangeTransportExtWarningDelegated' 4 | $ModuleName = 'MSOnline' 5 | $ModuleVersion = '1.1.166.0' 6 | $username = $Env:user 7 | $pw = $Env:password 8 | #import PS module 9 | $PSModulePath = "D:\home\site\wwwroot\$FunctionName\bin\$ModuleName\$ModuleVersion\$ModuleName.psd1" 10 | $res = "D:\home\site\wwwroot\$FunctionName\bin" 11 | 12 | Import-module $PSModulePath 13 | 14 | # Build Credentials 15 | $keypath = "D:\home\site\wwwroot\$FunctionName\bin\keys\PassEncryptKey.key" 16 | $secpassword = $pw | ConvertTo-SecureString -Key (Get-Content $keypath) 17 | $credential = New-Object System.Management.Automation.PSCredential ($username, $secpassword) 18 | 19 | # Connect to MSOnline 20 | Connect-MsolService -Credential $credential 21 | $customers = Get-MsolPartnerContract -All | Where-Object {$_.name -match "Customer1" -or $_.name -match "Customer2" -or $_.name -match "Customer3"} 22 | 23 | Write-Output "Found $($customers.Count) customers for $((Get-MsolCompanyInformation).displayname)." 24 | 25 | $ruleName = "Warn on external senders with matching display names" 26 | $ruleHtml = "

This message was sent from outside the company by someone with a display name matching a user in your organisation. Please do not click links or open attachments unless you recognise the source of this email and know the content is safe.

" 27 | 28 | foreach ($customer in $customers) { 29 | $InitialDomain = Get-MsolDomain -TenantId $customer.TenantId | Where-Object {$_.IsInitial -eq $true} 30 | 31 | Write-Output "Checking transport rule for $($Customer.Name)" 32 | $DelegatedOrgURL = "https://outlook.office365.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name 33 | $s = New-PSSession -ConnectionUri $DelegatedOrgURL -Credential $credential -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection 34 | Import-PSSession $s -CommandName Get-Mailbox, Get-TransportRule, New-TransportRule, Set-TransportRule -AllowClobber 35 | 36 | $rule = Get-TransportRule | Where-Object {$_.Identity -contains $ruleName} 37 | $displayNames = (Get-Mailbox -ResultSize Unlimited).DisplayName 38 | 39 | if (!$rule) { 40 | Write-Output "Rule not found, creating Rule" 41 | New-TransportRule -Name $ruleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" ` 42 | -HeaderMatchesMessageHeader From -HeaderMatchesPatterns $displayNames -ApplyHtmlDisclaimerText $ruleHtml 43 | } 44 | else { 45 | Write-Output "Rule found, updating Rule" 46 | Set-TransportRule -Identity $ruleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" ` 47 | -HeaderMatchesMessageHeader From -HeaderMatchesPatterns $displayNames -ApplyHtmlDisclaimerText $ruleHtml 48 | } 49 | 50 | Remove-PSSession $s -------------------------------------------------------------------------------- /Office365/General/Enable-AuditLogging/script2.ps1: -------------------------------------------------------------------------------- 1 | <# This script will use the admin users created by the first script to enable the Unified Audit Log in each tenant. If enabling the Unified Audit Log is successful, it'll remove the created admin. If it's not successful, it'll keep the admin in place and add it to another CSV. You can retry these tenants by modifying the $Customers value to import the RemainingAdminsCsv in the next run. #> 2 | 3 | #------------------------------------------------------------- 4 | 5 | # Here are some things you can modify: 6 | 7 | # This is your partner admin user name that has delegated administration permission 8 | 9 | $UserName = "training@gcits.com" 10 | 11 | # IMPORTANT: This is the default password for the temporary admin users. Use the same password that you specified in the first script. 12 | 13 | $NewAdminPassword = "Password123" 14 | 15 | # This is the CSV containing the details of the created admins generated by the first script. If you changed the path in the first script, you'll need to change it here. 16 | 17 | $Customers = import-csv "C:\temp\CreatedAdmins.csv" 18 | 19 | # This CSV will contain a list of all admins removed by this script. 20 | 21 | $RemovedAdminsCsv = "C:\temp\RemovedAdmins.csv" 22 | 23 | # This CSV will contain a list of all unsuccessful admins left unchanged by this script. Use it to retry this script without having to start again. 24 | 25 | $RemainingAdminsCsv = "C:\temp\RemainingAdmins.csv" 26 | 27 | #------------------------------------------------------------- 28 | 29 | $Cred = get-credential -Credential $UserName 30 | 31 | foreach ($Customer in $Customers) { 32 | 33 | Write-Host $Customer.CompanyName.ToUpper() 34 | Write-Host " " 35 | 36 | 37 | $NewAdminUPN = $Customer.UserPrincipalName 38 | 39 | $secpasswd = ConvertTo-SecureString $NewAdminPassword -AsPlainText -Force 40 | 41 | $NewAdminCreds = New-Object System.Management.Automation.PSCredential ($NewAdminUPN, $secpasswd) 42 | 43 | Write-Host " " 44 | 45 | Write-Output "Getting the Exchange Online cmdlets as $NewAdminUPN" 46 | 47 | $Session = New-PSSession -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` 48 | -ConfigurationName Microsoft.Exchange -Credential $NewAdminCreds ` 49 | -Authentication Basic -AllowRedirection 50 | Import-PSSession $Session -AllowClobber 51 | 52 | # Enable the customization of the Exchange Organisation 53 | 54 | Enable-OrganizationCustomization 55 | 56 | # Enable the Unified Audit Log 57 | 58 | Set-AdminAuditLogConfig -UnifiedAuditLogIngestionEnabled $true 59 | 60 | # Find out whether it worked 61 | 62 | $AuditLogConfigResult = Get-AdminAuditLogConfig 63 | 64 | Remove-PSSession $Session 65 | 66 | # If it worked, remove the Admin and add the removed admin details to a CSV 67 | 68 | if ($AuditLogConfigResult.UnifiedAuditLogIngestionEnabled) { 69 | 70 | # Remove the temporary admin 71 | Write-Host "Removing the temporary Admin" 72 | 73 | Remove-MsolUser -TenantId $Customer.TenantId -UserPrincipalName $NewAdminUPN -Force 74 | 75 | $AdminProperties = @{ 76 | TenantId = $Customer.TenantId 77 | CompanyName = $Customer.CompanyName 78 | DefaultDomainName = $Customer.DefaultDomainName 79 | UserPrincipalName = $NewAdminUPN 80 | Action = "REMOVED" 81 | } 82 | 83 | $RemovedAdmins = @() 84 | $RemovedAdmins += New-Object psobject -Property $AdminProperties 85 | $RemovedAdmins | Select-Object TenantId, CompanyName, DefaultDomainName, UserPrincipalName, Action | Export-Csv -notypeinformation -Path $RemovedAdminsCsv -Append 86 | 87 | } 88 | 89 | # If it didn't work, keep the Admin and add the admin details to another CSV. You can use the RemainingAdmins CSV if you'd like to try again. 90 | 91 | if (!$AuditLogConfigResult.UnifiedAuditLogIngestionEnabled) { 92 | 93 | Write-Host "Enabling Audit Log Failed, keeping the temporary Admin" 94 | 95 | $AdminProperties = @{ 96 | TenantId = $Customer.TenantId 97 | CompanyName = $Customer.CompanyName 98 | DefaultDomainName = $Customer.DefaultDomainName 99 | UserPrincipalName = $NewAdminUPN 100 | Action = "UNCHANGED" 101 | } 102 | 103 | $RemainingAdmins = @() 104 | $RemainingAdmins += New-Object psobject -Property $AdminProperties 105 | $RemainingAdmins | Select-Object TenantId, CompanyName, DefaultDomainName, UserPrincipalName, Action | Export-Csv -notypeinformation -Path $RemainingAdminsCsv -Append 106 | 107 | 108 | } 109 | 110 | Write-Host " " 111 | Write-Host "----------------------------------------------------------" 112 | Write-Host " " 113 | 114 | } -------------------------------------------------------------------------------- /Office365/OneDrive/Transfer-OneDrivetoOneDrive/sinlge.ps1: -------------------------------------------------------------------------------- 1 | $departinguser = Read-Host "Enter departing user's email" 2 | $destinationuser = Read-Host "Enter destination user's email" 3 | $globaladmin = Read-Host "Enter the username of your Global Admin account" 4 | $credentials = Get-Credential -Credential $globaladmin 5 | Connect-MsolService -Credential $credentials 6 | 7 | $InitialDomain = Get-MsolDomain | Where-Object {$_.IsInitial -eq $true} 8 | 9 | $SharePointAdminURL = "https://$($InitialDomain.Name.Split(".")[0])-admin.sharepoint.com" 10 | 11 | $departingUserUnderscore = $departinguser -replace "[^a-zA-Z]", "_" 12 | $destinationUserUnderscore = $destinationuser -replace "[^a-zA-Z]", "_" 13 | 14 | $departingOneDriveSite = "https://$($InitialDomain.Name.Split(".")[0])-my.sharepoint.com/personal/$departingUserUnderscore" 15 | $destinationOneDriveSite = "https://$($InitialDomain.Name.Split(".")[0])-my.sharepoint.com/personal/$destinationUserUnderscore" 16 | Write-Host "`nConnecting to SharePoint Online" -ForegroundColor Blue 17 | Connect-SPOService -Url $SharePointAdminURL -Credential $credentials 18 | 19 | Write-Host "`nAdding $globaladmin as site collection admin on both OneDrive site collections" -ForegroundColor Blue 20 | # Set current admin as a Site Collection Admin on both OneDrive Site Collections 21 | Set-SPOUser -Site $departingOneDriveSite -LoginName $globaladmin -IsSiteCollectionAdmin $true 22 | Set-SPOUser -Site $destinationOneDriveSite -LoginName $globaladmin -IsSiteCollectionAdmin $true 23 | 24 | Write-Host "`nConnecting to $departinguser's OneDrive via SharePoint Online PNP module" -ForegroundColor Blue 25 | 26 | Connect-PnPOnline -Url $departingOneDriveSite -Credentials $credentials 27 | 28 | Write-Host "`nGetting display name of $departinguser" -ForegroundColor Blue 29 | # Get name of departing user to create folder name. 30 | $departingOwner = Get-PnPSiteCollectionAdmin | Where-Object {$_.loginname -match $departinguser} 31 | 32 | # If there's an issue retrieving the departing user's display name, set this one. 33 | if ($departingOwner -contains $null) { 34 | $departingOwner = @{ 35 | Title = "Departing User" 36 | } 37 | } 38 | 39 | # Define relative folder locations for OneDrive source and destination 40 | $departingOneDrivePath = "/personal/$departingUserUnderscore/Documents" 41 | $destinationOneDrivePath = "/personal/$destinationUserUnderscore/Documents/$($departingOwner.Title)'s Files" 42 | $destinationOneDriveSiteRelativePath = "Documents/$($departingOwner.Title)'s Files" 43 | 44 | Write-Host "`nGetting all items from $($departingOwner.Title)" -ForegroundColor Blue 45 | # Get all items from source OneDrive 46 | $items = Get-PnPListItem -List Documents -PageSize 1000 47 | 48 | $largeItems = $items | Where-Object {[long]$_.fieldvalues.SMTotalFileStreamSize -ge 261095424 -and $_.FileSystemObjectType -contains "File"} 49 | if ($largeItems) { 50 | $largeexport = @() 51 | foreach ($item in $largeitems) { 52 | $largeexport += "$(Get-Date) - Size: $([math]::Round(($item.FieldValues.SMTotalFileStreamSize / 1MB),2)) MB Path: $($item.FieldValues.FileRef)" 53 | Write-Host "File too large to copy: $($item.FieldValues.FileRef)" -ForegroundColor DarkYellow 54 | } 55 | $largeexport | Out-file C:\temp\largefiles.txt -Append 56 | Write-Host "A list of files too large to be copied from $($departingOwner.Title) have been exported to C:\temp\LargeFiles.txt" -ForegroundColor Yellow 57 | } 58 | 59 | $rightSizeItems = $items | Where-Object {[long]$_.fieldvalues.SMTotalFileStreamSize -lt 261095424 -or $_.FileSystemObjectType -contains "Folder"} 60 | 61 | Write-Host "`nConnecting to $destinationuser via SharePoint PNP PowerShell module" -ForegroundColor Blue 62 | Connect-PnPOnline -Url $destinationOneDriveSite -Credentials $credentials 63 | 64 | Write-Host "`nFilter by folders" -ForegroundColor Blue 65 | # Filter by Folders to create directory structure 66 | $folders = $rightSizeItems | Where-Object {$_.FileSystemObjectType -contains "Folder"} 67 | 68 | Write-Host "`nCreating Directory Structure" -ForegroundColor Blue 69 | foreach ($folder in $folders) { 70 | $path = ('{0}{1}' -f $destinationOneDriveSiteRelativePath, $folder.fieldvalues.FileRef).Replace($departingOneDrivePath, '') 71 | Write-Host "Creating folder in $path" -ForegroundColor Green 72 | $newfolder = Ensure-PnPFolder -SiteRelativePath $path 73 | } 74 | 75 | 76 | Write-Host "`nCopying Files" -ForegroundColor Blue 77 | $files = $rightSizeItems | Where-Object {$_.FileSystemObjectType -contains "File"} 78 | $fileerrors = "" 79 | foreach ($file in $files) { 80 | 81 | $destpath = ("$destinationOneDrivePath$($file.fieldvalues.FileDirRef)").Replace($departingOneDrivePath, "") 82 | Write-Host "Copying $($file.fieldvalues.FileLeafRef) to $destpath" -ForegroundColor Green 83 | $newfile = Copy-PnPFile -SourceUrl $file.fieldvalues.FileRef -TargetUrl $destpath -OverwriteIfAlreadyExists -Force -ErrorVariable errors -ErrorAction SilentlyContinue 84 | $fileerrors += $errors 85 | } 86 | $fileerrors | Out-File c:\temp\fileerrors.txt 87 | 88 | # Remove Global Admin from Site Collection Admin role for both users 89 | Write-Host "`nRemoving $globaladmin from OneDrive site collections" -ForegroundColor Blue 90 | Set-SPOUser -Site $departingOneDriveSite -LoginName $globaladmin -IsSiteCollectionAdmin $false 91 | Set-SPOUser -Site $destinationOneDriveSite -LoginName $globaladmin -IsSiteCollectionAdmin $false 92 | Write-Host "`nComplete!" -ForegroundColor Green -------------------------------------------------------------------------------- /ITGlue/HaveIBeenPwnedPasswordCheck/README.md: -------------------------------------------------------------------------------- 1 | [Source](https://gcits.com/knowledge-base/check-it-glue-passwords-against-have-i-been-pwned-breaches/ "Permalink to Check IT Glue passwords against Have I Been Pwned breaches") 2 | 3 | # Check IT Glue passwords against Have I Been Pwned breaches 4 | 5 | ![Use PowerShell to check IT Glue Passwords against Have I Been Pwned Breaches][1] 6 | 7 | Hackers will often use password spray attacks to gain access to accounts. These attacks work by trying a commonly used password against many accounts. 8 | 9 | If you're using the IT Glue documentation system, you can use this script to determine how secure and common the passwords in your customer environment are by checking for their presence in known data breaches. 10 | 11 | It works by retrieving your IT Glue Password list via the IT Glue API and run each password through the Have I Been Pwned, Pwned Password API. If a match is detected, its details will be exported to a CSV along with the how many times the password has been detected in a breach. 12 | 13 | ## How to check your customers' passwords against Have I Been Pwned data breaches. 14 | 15 | #### Retrieve your IT Glue Api Key with password access 16 | 17 | 1. Sign into IT Glue as an Administrator 18 | 2. Navigate to Account, Settings, Api Keys 19 | ![Log Into IT Glue Settings][2] 20 | 3. Under Custom API Keys, Generate a New Key, give it a sample name and tick the Password Access box 21 | ![ Create IT Glue Password Access API Key][3] 22 | 4. Treat this key very carefully as it can be used to access all passwords in your ITGlue environment. I recommend disabling password access and revoking the key once you have run the script. 23 | 24 | #### How to run the script to detect customer passwords in known HIBP data breaches 25 | 26 | 1. Double click the below script to select it. 27 | 2. Copy and Paste it into **Visual Studio Code** 28 | 3. Save it with a **.ps1** extension 29 | 4. Install the recommended PowerShell extension in Visual Studio Code if you haven't already 30 | 5. Copy and paste the API key you created earlier into the **$key** variable in the PowerShell script. 31 | 6. If you are in the EU, you may need to update the **$baseURI** value to "https://api.eu.itglue.com" 32 | 7. Press **F5** to run the script. 33 | 34 | ![IT Glue Passwords Detected In Breaches][4] 35 | 36 | 8. A report of all found passwords will be exported to a CSV at **C:temppwnedpasswords.csv**. While this CSV does not contain the passwords, it does contain the usernames and other potentially sensitive information. 37 | 9. The FoundCount column in the CSV is the number of times the password has been found in a HIBP reported breach. 38 | ![Pwned Password CSV][5] 39 | 40 | You can use this CSV to assist with resetting passwords and improving the security of your customers' environments. 41 | 42 | ## Script to check IT Glue passwords against have I Been Pwned data breaches 43 | 44 | ```powershell 45 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 46 | 47 | $key = "ENTERAPIKEYHERE" 48 | $ITGbaseURI = "https://api.itglue.com" 49 | 50 | $headers = @{ 51 | "x-api-key" = $key 52 | } 53 | 54 | Function Get-StringHash([String] $String, $HashName = "MD5") { 55 | $StringBuilder = New-Object System.Text.StringBuilder 56 | [System.Security.Cryptography.HashAlgorithm]::Create($HashName).ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String))| % { 57 | [Void]$StringBuilder.Append($_.ToString("x2")) 58 | } 59 | $StringBuilder.ToString() 60 | } 61 | 62 | function Get-ITGlueItem($Resource) { 63 | $array = @() 64 | 65 | $body = Invoke-RestMethod -Method get -Uri "$ITGbaseUri/$Resource" -Headers $headers -ContentType application/vnd.api+json 66 | $array += $body.data 67 | Write-Host "Retrieved $($array.Count) items" 68 | 69 | if ($body.links.next) { 70 | do { 71 | $body = Invoke-RestMethod -Method get -Uri $body.links.next -Headers $headers -ContentType application/vnd.api+json 72 | $array += $body.data 73 | Write-Host "Retrieved $($array.Count) items" 74 | } while ($body.links.next) 75 | } 76 | return $array 77 | } 78 | 79 | $passwords = Get-ITGlueItem -Resource passwords 80 | 81 | foreach($password in $passwords){ 82 | $details = Get-ITGlueItem -Resource passwords/$($password.id) 83 | $hash = Get-StringHash -String $details.attributes.password -HashName SHA1 84 | $first5 = $hash.Substring(0,5) 85 | $remaining = $hash.Substring(5) 86 | $result = Invoke-Restmethod -Uri "https://api.pwnedpasswords.com/range/$first5" 87 | $result = $result -split "`n" 88 | $match = $result | Where-Object {$_ -match $remaining} 89 | if($match){ 90 | $FoundCount = ($match -split ":")[1] 91 | Write-Host $FoundCount -ForegroundColor Red 92 | Write-Host "Found $($details.attributes.'organization-name') - $($details.attributes.name)`n" -ForegroundColor Yellow 93 | $password.attributes | Add-Member FoundCount $FoundCount -Force 94 | $password.attributes | export-csv C:temppwnedpasswords.csv -NoTypeInformation -Append 95 | } 96 | } 97 | ``` 98 | 99 | ### About The Author 100 | 101 | ![Elliot Munro][6] 102 | 103 | #### [ Elliot Munro ][7] 104 | 105 | Elliot Munro is an Office 365 MCSA from the Gold Coast, Australia supporting hundreds of small businesses with GCITS. If you have an Office 365 or Azure issue that you'd like us to take a look at (or have a request for a useful script) send Elliot an email at [elliot@gcits.com][8] 106 | 107 | [1]: https://gcits.com/wp-content/uploads/PowerShellITGluePasswords-1030x436.png 108 | [2]: https://gcits.com/wp-content/uploads/LogIntoITGlueSettings.png 109 | [3]: https://gcits.com/wp-content/uploads/CreatePasswordAccessAPIKey.png 110 | [4]: https://gcits.com/wp-content/uploads/ITGluePasswordsDetectedInBreaches.png 111 | [5]: https://gcits.com/wp-content/uploads/PwnedPasswordCSV.png 112 | [6]: https://gcits.com/wp-content/uploads/AAEAAQAAAAAAAA2QAAAAJDNlN2NmM2Y4LTU5YWYtNGRiNC1hMmI2LTBhMzdhZDVmNWUzNA-80x80.jpg 113 | [7]: https://gcits.com/author/elliotmunro/ 114 | [8]: mailto:elliot%40gcits.com 115 | -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Enable-MailboxAuditing/README.md: -------------------------------------------------------------------------------- 1 | [Source](https://gcits.com/knowledge-base/enable-mailbox-auditing-on-all-users-for-all-office-365-customer-tenants/ "Permalink to Enable mailbox auditing on all users for all Office 365 customer tenants") 2 | 3 | # Enable mailbox auditing on all users for all Office 365 customer tenants 4 | 5 | To increase your Office 365 security score, it's recommended that you enable mailbox auditing for all users. This isn't switched on by default, however it's very easy to apply using PowerShell. 6 | Mailbox auditing allows you to track actions that users take within their own and other's mailboxes. Using this feature, you can search the Office 365 Unified Audit logs by mailbox actions and the users that performed them. You can also set up alerts and receive notifications when a user permanently deletes mail, or sends as another user. 7 | 8 | As well as mailbox auditing, we also recommend you enable the Unified Audit Log for your organisation(s). [Here's our guide on how to configure this][1]. 9 | 10 | ## How to enable Office 365 Mailbox Auditing for a single Office 365 tenant 11 | 12 | If you're just managing a single Office 365 tenant, you can connect to Exchange Online via Powershell and run this simple script. 13 | 14 | 1. Connect to Exchange Online via PowerShell. [See this quick guide][2] for info on how to set this up. 15 | 2. Run the following cmdlet: 16 | 17 | ```powershell 18 | Get-Mailbox -ResultSize Unlimited | Set-Mailbox -AuditEnabled $true -AuditOwner MailboxLogin,HardDelete,SoftDelete,Update,Move -AuditDelegate SendOnBehalf,MoveToDeletedItems,Move -AuditAdmin Copy,MessageBind 19 | ``` 20 | 21 | ## How to enable Office 365 Mailbox Auditing for all Office 365 customer tenants 22 | 23 | If you're an IT partner managing a number of Office 365 organisations with delegated administration, you can enable mailbox auditing for all your customers' users in one go. 24 | 25 | Run this script using Visual Studio Code or PowerShell ISE. When prompted, sign in with the credentials of a user which has delegated administration access to your customers' Office 365 tenants. 26 | 27 | ```powershell 28 | $ScriptBlock = {Get-Mailbox -ResultSize Unlimited | Set-Mailbox -AuditEnabled $true -AuditOwner MailboxLogin, HardDelete, SoftDelete, Update, Move -AuditDelegate SendOnBehalf, MoveToDeletedItems, Move -AuditAdmin Copy, MessageBind} 29 | 30 | # Establish a PowerShell session with Office 365. You'll be prompted for your Delegated Admin credentials 31 | 32 | $Cred = Get-Credential 33 | Connect-MsolService -Credential $Cred 34 | $customers = Get-MsolPartnerContract -All 35 | Write-Host "Found $($customers.Count) customers for this Partner." 36 | 37 | foreach ($customer in $customers) { 38 | 39 | $InitialDomain = Get-MsolDomain -TenantId $customer.TenantId | Where-Object {$\_.IsInitial -eq $true} 40 | Write-Host "Enabling Mailbox Auditing for $($Customer.Name)" 41 | $DelegatedOrgURL = "https://ps.outlook.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name 42 | Invoke-Command -ConnectionUri $DelegatedOrgURL -Credential $Cred -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection -ScriptBlock $ScriptBlock -HideComputerName 43 | } 44 | ``` 45 | 46 | ![Enable Mailbox Auditing in Office 365 via PowerShell][3] 47 | 48 | ## How to enable Office 365 Mailbox Auditing for all Office 365 customer tenants with Azure Functions 49 | 50 | You can also ensure that all future mailboxes have Mailbox auditing switched on every day using an Azure Function running in the cloud. 51 | 52 | [Use this guide to set up an Azure Function][4] that connects to Office 365 using the MSOnline Powershell Module. 53 | 54 | - Set it to run once a day using the following timer trigger: 55 | 0 0 12 \* \* \* 56 | - Make sure that you've set up the encrypted credentials for your delegated admin user, and the MSOnline PowerShell module as per the above guide. 57 | - Name your function EnableMailboxAuditing, or update the code below to refer to the new name. 58 | 59 | Replace the function code with the following: 60 | 61 | ```powershell 62 | Write-Output "PowerShell Timer trigger function executed at:$(get-date)"; 63 | 64 | $FunctionName = 'EnableMailboxAuditing' 65 | $ModuleName = 'MSOnline' 66 | $ModuleVersion = '1.1.166.0' 67 | $username = $Env:user 68 | $pw = $Env:password 69 | #import PS module 70 | $PSModulePath = "D:homesitewwwroot$FunctionNamebin$ModuleName$ModuleVersion$ModuleName.psd1" 71 | $res = "D:homesitewwwroot$FunctionNamebin" 72 | 73 | Import-module $PSModulePath 74 | 75 | # Build Credentials 76 | $keypath = "D:homesitewwwroot$FunctionNamebinkeysPassEncryptKey.key" 77 | $secpassword = $pw | ConvertTo-SecureString -Key (Get-Content $keypath) 78 | $credential = New-Object System.Management.Automation.PSCredential ($username, $secpassword) 79 | 80 | # Connect to MSOnline 81 | 82 | Connect-MsolService -Credential $credential 83 | 84 | $ScriptBlock = {Get-Mailbox | Set-Mailbox -AuditEnabled $true -AuditOwner MailboxLogin, HardDelete, SoftDelete, Update, Move -AuditDelegate SendOnBehalf, MoveToDeletedItems, Move -AuditAdmin Copy, MessageBind} 85 | 86 | $customers = Get-MsolPartnerContract -All 87 | Write-Output "Found $($customers.Count) customers for this Partner." 88 | 89 | foreach ($customer in $customers) { 90 | 91 | $InitialDomain = Get-MsolDomain -TenantId $customer.TenantId | Where-Object {$\_.IsInitial -eq $true} 92 | Write-Output "Enabling Mailbox Auditing for $($Customer.Name)" 93 | $DelegatedOrgURL = "https://ps.outlook.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name 94 | Invoke-Command -ConnectionUri $DelegatedOrgURL -Credential $credential -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection -ScriptBlock $ScriptBlock -HideComputerName 95 | } 96 | ``` 97 | 98 | ### About The Author 99 | 100 | ![Elliot Munro][5] 101 | 102 | #### [ Elliot Munro ][6] 103 | 104 | Elliot Munro is an Office 365 MCSA from the Gold Coast, Australia supporting hundreds of small businesses with GCITS. If you have an Office 365 or Azure issue that you'd like us to take a look at (or have a request for a useful script) send Elliot an email at [elliot@gcits.com][7] 105 | 106 | [1]: https://gcits.com/knowledge-base/enabling-unified-audit-log-delegated-office-365-tenants-via-powershell/ 107 | [2]: https://gcits.com/knowledge-base/how-to-set-up-a-quick-connection-to-exchange-online-via-powershell/ 108 | [3]: https://gcits.com/wp-content/uploads/EnableMailboxAuditingOffice365PowerShell.png 109 | [4]: https://gcits.com/knowledge-base/connect-azure-function-office-365/ 110 | [5]: https://gcits.com/wp-content/uploads/AAEAAQAAAAAAAA2QAAAAJDNlN2NmM2Y4LTU5YWYtNGRiNC1hMmI2LTBhMzdhZDVmNWUzNA-80x80.jpg 111 | [6]: https://gcits.com/author/elliotmunro/ 112 | [7]: mailto:elliot%40gcits.com 113 | -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Enable-IncreaseE3MailboxSizeTo100GB/README.md: -------------------------------------------------------------------------------- 1 | [Source](https://gcits.com/knowledge-base/increase-office-365-e3-mailboxes-100-gb-via-powershell/ "Permalink to Increase all Office 365 E3 mailboxes to 100 GB via PowerShell") 2 | 3 | # Increase all Office 365 E3 mailboxes to 100 GB via PowerShell 4 | 5 | ![Increase Office 365 E3 Mailboxes to 100 GB][1] 6 | Back in 2016, Microsoft announced that they were increasing the maximum mailbox size for certain Office 365 licenses from 50GB to 100GB. 7 | 8 | For most mailboxes, this increase took place automatically. 9 | 10 | We had an issue this week where a user on the Office 365 E3 license was hitting a 50GB limit. Upon investigation, we discovered four other mailboxes in the tenant that were also only 50GB with E3 licenses. 11 | 12 | To resolve this issue, we can increase the maximum mailbox sizes via PowerShell. The below scripts will handle this for you. 13 | 14 | The first one works for a single Office 365 tenant, while the second is for Microsoft Partners, and allows them to detect and remedy the issue across all customer tenants. 15 | 16 | The script works by logging into Office 365 via PowerShell and retrieving users with a license type that qualifies for a 100GB mailbox. If users are detected, it will log onto Exchange Online and retrieve the relevant mailboxes. If the mailboxes have a ProbitSendAndReceiveQuota of 50 GB, they will be resized to 100GB. A report will be exported to C:tempmailboxresizereport.csv 17 | 18 | ## How to run the scripts to increase Office 365 E3 mailboxes to 100 GB 19 | 20 | 1. Copy and paste one of the scripts below into Visual Studio Code and save it as a .ps1 file 21 | 2. Press F5 to run it 22 | 3. Enter your Office 365 Admin Credentials – or delegated admin credentials for the second script. Note that these scripts don't work with MFA. 23 | 4. Wait for it to complete. ![Script To Resize Office 365 E3 Mailboxes To 100 GB][2] 24 | 5. A report will be exported to c:tempmailboxresizereport.csv![Office 365 E3 Resized Mailbox Report][3] 25 | 26 | ## PowerShell script to increase the mailbox size for all Office E3 mailboxes to 100GB in a single tenant 27 | 28 | ```powershell 29 | $credential = Get-Credential 30 | Connect-MsolService -Credential $credential 31 | 32 | $users = Get-MsolUser -All | where-object {$_.licenses.accountskuid -match "enterprisepack" -or ` 33 | $_.licenses.accountskuid -match "SPE_E3" -or $_.licenses.accountskuid -match "SPE_E5" ` 34 | -or $_.licenses.accountskuid -match "EXCHANGE_S_ENTERPRISE"} 35 | 36 | if ($users) { 37 | $Session = New-PSSession -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` 38 | -ConfigurationName Microsoft.Exchange -Credential $credential ` 39 | -Authentication Basic -AllowRedirection 40 | Import-PSSession $Session -CommandName Get-Mailbox, Set-Mailbox 41 | 42 | 43 | $reports = @() 44 | foreach ($user in $users) { 45 | $mailbox = get-mailbox $user.userprincipalname 46 | if ($mailbox.ProhibitSendReceiveQuota -match "50 GB") { 47 | Write-Host "$($mailbox.displayname) is only 50 GB, resizing..." -ForegroundColor Yellow 48 | Set-Mailbox $mailbox.PrimarySmtpAddress -ProhibitSendReceiveQuota 100GB -ProhibitSendQuota 99GB -IssueWarningQuota 98GB 49 | $mailboxCheck = get-mailbox $mailbox.PrimarySmtpAddress 50 | Write-Host "New mailbox maximum size is $($mailboxcheck.ProhibitSendReceiveQuota)" -ForegroundColor Green 51 | $reportHash = @{ 52 | DisplayName = $user.DisplayName 53 | PrimarySmtpAddress = $mailbox.PrimarySmtpAddress 54 | Licenses = $user.licenses.accountskuid -join ", " 55 | ProhibitSendReceiveQuota = $mailboxCheck.ProhibitSendReceiveQuota 56 | 57 | } 58 | $reportObject = New-Object psobject -Property $reportHash 59 | $reports += $reportObject 60 | } 61 | } 62 | 63 | $reports | export-csv C:tempMailboxResizeReport.csv -NoTypeInformation -Append 64 | Remove-PSSession $Session 65 | } 66 | ``` 67 | 68 | ## PowerShell script to increase the mailbox size for all Office E3 mailboxes to 100GB in all customer tenants 69 | 70 | ```powershell 71 | $credential = Get-Credential 72 | Connect-MsolService -Credential $credential 73 | 74 | $customers = Get-MsolPartnerContract 75 | 76 | foreach ($customer in $customers) { 77 | Write-Host "Checking Mailbox Sizes for $($customer.name)" -ForegroundColor Green 78 | $users = $null 79 | $users = Get-MsolUser -TenantId $customer.tenantid | where-object {$_.licenses.accountskuid -match "enterprisepack" -or ` 80 | $_.licenses.accountskuid -match "SPE_E3" -or $_.licenses.accountskuid -match "SPE_E5" ` 81 | -or $_.licenses.accountskuid -match "EXCHANGE_S_ENTERPRISE"} 82 | 83 | if ($users) { 84 | $InitialDomain = Get-MsolDomain -TenantId $customer.tenantid | Where-Object {$_.IsInitial -eq $true} 85 | $DelegatedOrgURL = "https://outlook.office365.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name 86 | $EXODS = New-PSSession -ConnectionUri $DelegatedOrgURL -Credential $credential -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection 87 | Import-PSSession $EXODS -CommandName Get-Mailbox, Set-Mailbox -AllowClobber 88 | 89 | $reports = @() 90 | foreach ($user in $users) { 91 | $mailbox = get-mailbox $user.userprincipalname 92 | if ($mailbox.ProhibitSendReceiveQuota -match "50 GB") { 93 | Write-Host "$($mailbox.displayname) is only 50 GB, resizing..." -ForegroundColor Yellow 94 | Set-Mailbox $mailbox.PrimarySmtpAddress -ProhibitSendReceiveQuota 100GB -ProhibitSendQuota 99GB -IssueWarningQuota 98GB 95 | $mailboxCheck = get-mailbox $mailbox.PrimarySmtpAddress 96 | Write-Host "New mailbox maximum size is $($mailboxcheck.ProhibitSendReceiveQuota)" -ForegroundColor Green 97 | $reportHash = @{ 98 | CustomerName = $customer.Name 99 | TenantId = $customer.TenantId 100 | DisplayName = $user.DisplayName 101 | PrimarySmtpAddress = $mailbox.PrimarySmtpAddress 102 | Licenses = $user.licenses.accountskuid -join ", " 103 | ProhibitSendReceiveQuota = $mailboxCheck.ProhibitSendReceiveQuota 104 | 105 | } 106 | $reportObject = New-Object psobject -Property $reportHash 107 | $reports += $reportObject 108 | } 109 | } 110 | $reports | export-csv C:tempMailboxResizeReport.csv -NoTypeInformation -Append 111 | Remove-PSSession $EXODS 112 | } 113 | } 114 | ``` 115 | 116 | ### About The Author 117 | 118 | ![Elliot Munro][4] 119 | 120 | #### [Elliot Munro][5] 121 | 122 | Elliot Munro is an Office 365 MCSA from the Gold Coast, Australia supporting hundreds of small businesses with GCITS. If you have an Office 365 or Azure issue that you'd like us to take a look at (or have a request for a useful script) send Elliot an email at [elliot@gcits.com][6] 123 | 124 | [1]: https://gcits.com/wp-content/uploads/RemoveUnnecessaryLicensesOffice365SharedMailbox-1030x436.png 125 | [2]: https://gcits.com/wp-content/uploads/ScriptToResizeOffice365E3MailboxesTo100GB.png 126 | [3]: https://gcits.com/wp-content/uploads/Office365E3ResizedMailboxReport.png 127 | [4]: https://gcits.com/wp-content/uploads/AAEAAQAAAAAAAA2QAAAAJDNlN2NmM2Y4LTU5YWYtNGRiNC1hMmI2LTBhMzdhZDVmNWUzNA-80x80.jpg 128 | [5]: https://gcits.com/author/elliotmunro/ 129 | [6]: mailto:elliot%40gcits.com 130 | -------------------------------------------------------------------------------- /ITGlue/Office365Sync/single.ps1: -------------------------------------------------------------------------------- 1 | $key = "ENTERITGLUEAPIKEYHERE" 2 | $assettypeID = 9999 3 | $baseURI = "https://api.itglue.com" 4 | $headers = @{ 5 | "x-api-key" = $key 6 | } 7 | 8 | $credential = Get-Credential 9 | Connect-MsolService -Credential $credential 10 | 11 | function GetAllITGItems($Resource) { 12 | $array = @() 13 | 14 | $body = Invoke-RestMethod -Method get -Uri "$baseUri/$Resource" -Headers $headers -ContentType application/vnd.api+json 15 | $array += $body.data 16 | Write-Host "Retrieved $($array.Count) items" 17 | 18 | if ($body.links.next) { 19 | do { 20 | $body = Invoke-RestMethod -Method get -Uri $body.links.next -Headers $headers -ContentType application/vnd.api+json 21 | $array += $body.data 22 | Write-Host "Retrieved $($array.Count) items" 23 | } while ($body.links.next) 24 | } 25 | return $array 26 | } 27 | 28 | function CreateITGItem ($resource, $body) { 29 | $item = Invoke-RestMethod -Method POST -ContentType application/vnd.api+json -Uri $baseURI/$resource -Body $body -Headers $headers 30 | return $item 31 | } 32 | 33 | function UpdateITGItem ($resource, $existingItem, $newBody) { 34 | $updatedItem = Invoke-RestMethod -Method Patch -Uri "$baseUri/$Resource/$($existingItem.id)" -Headers $headers -ContentType application/vnd.api+json -Body $newBody 35 | return $updatedItem 36 | } 37 | 38 | function Build365TenantAsset ($tenantInfo) { 39 | 40 | $body = @{ 41 | data = @{ 42 | type = "flexible-assets" 43 | attributes = @{ 44 | "organization-id" = $tenantInfo.OrganizationID 45 | "flexible-asset-type-id" = $assettypeID 46 | traits = @{ 47 | "tenant-name" = $tenantInfo.TenantName 48 | "tenant-id" = $tenantInfo.TenantID 49 | "initial-domain" = $tenantInfo.InitialDomain 50 | "verified-domains" = $tenantInfo.Domains 51 | "licenses" = $tenantInfo.Licenses 52 | "licensed-users" = $tenantInfo.LicensedUsers 53 | } 54 | } 55 | } 56 | } 57 | 58 | $tenantAsset = $body | ConvertTo-Json -Depth 10 59 | return $tenantAsset 60 | } 61 | 62 | 63 | 64 | $customers = Get-MsolPartnerContract -All 65 | 66 | $365domains = @() 67 | 68 | foreach ($customer in $customers) { 69 | Write-Host "Getting domains for $($customer.name)" -ForegroundColor Green 70 | $companyInfo = Get-MsolCompanyInformation -TenantId $customer.TenantId 71 | 72 | $customerDomains = Get-MsolDomain -TenantId $customer.TenantId | Where-Object {$_.status -contains "Verified"} 73 | $initialDomain = $customerDomains | Where-Object {$_.isInitial} 74 | $Licenses = $null 75 | $licenseTable = $null 76 | $Licenses = Get-MsolAccountSku -TenantId $customer.TenantId 77 | if ($licenses) { 78 | $licenseTableTop = "
License NameActiveConsumedUnused
" 79 | $licenseTableBottom = "
" 80 | $licensesColl = @() 81 | foreach ($license in $licenses) { 82 | $licenseString = "$($license.SkuPartNumber)$($license.ActiveUnits) active$($license.ConsumedUnits) consumed$($license.ActiveUnits - $license.ConsumedUnits) unused" 83 | $licensesColl += $licenseString 84 | } 85 | if ($licensesColl) { 86 | $licenseString = $licensesColl -join "" 87 | } 88 | $licenseTable = "{0}{1}{2}" -f $licenseTableTop, $licenseString, $licenseTableBottom 89 | } 90 | $licensedUserTable = $null 91 | $licensedUsers = $null 92 | $licensedUsers = get-msoluser -TenantId $customer.TenantId -All | Where-Object {$_.islicensed} | Sort-Object UserPrincipalName 93 | if ($licensedUsers) { 94 | $licensedUsersTableTop = "
Display NameAddressesAssigned Licenses
" 95 | $licensedUsersTableBottom = "
" 96 | $licensedUserColl = @() 97 | foreach ($user in $licensedUsers) { 98 | 99 | $aliases = (($user.ProxyAddresses | Where-Object {$_ -cnotmatch "SMTP" -and $_ -notmatch ".onmicrosoft.com"}) -replace "SMTP:", " ") -join "
" 100 | $licensedUserString = "$($user.DisplayName)$($user.UserPrincipalName)
$aliases$(($user.Licenses.accountsku.skupartnumber) -join "
")" 101 | $licensedUserColl += $licensedUserString 102 | } 103 | if ($licensedUserColl) { 104 | $licensedUserString = $licensedUserColl -join "" 105 | } 106 | $licensedUserTable = "{0}{1}{2}" -f $licensedUsersTableTop, $licensedUserString, $licensedUsersTableBottom 107 | 108 | 109 | } 110 | 111 | 112 | $hash = [ordered]@{ 113 | TenantName = $companyInfo.displayname 114 | PartnerTenantName = $customer.name 115 | Domains = $customerDomains.name 116 | TenantId = $customer.TenantId 117 | InitialDomain = $initialDomain.name 118 | Licenses = $licenseTable 119 | LicensedUsers = $licensedUserTable 120 | } 121 | $object = New-Object psobject -Property $hash 122 | $365domains += $object 123 | 124 | } 125 | 126 | # Get all organisations 127 | #$orgs = GetAllITGItems -Resource organizations 128 | 129 | # Get all Contacts 130 | $itgcontacts = GetAllITGItems -Resource contacts 131 | 132 | $itgEmailRecords = @() 133 | foreach ($contact in $itgcontacts) { 134 | foreach ($email in $contact.attributes."contact-emails") { 135 | $hash = @{ 136 | Domain = ($email.value -split "@")[1] 137 | OrganizationID = $contact.attributes.'organization-id' 138 | } 139 | $object = New-Object psobject -Property $hash 140 | $itgEmailRecords += $object 141 | } 142 | } 143 | 144 | $allMatches = @() 145 | foreach ($365tenant in $365domains) { 146 | foreach ($domain in $365tenant.Domains) { 147 | $itgContactMatches = $itgEmailRecords | Where-Object {$_.domain -contains $domain} 148 | foreach ($match in $itgContactMatches) { 149 | $hash = [ordered]@{ 150 | Key = "$($365tenant.TenantId)-$($match.OrganizationID)" 151 | TenantName = $365tenant.TenantName 152 | Domains = ($365tenant.domains -join ", ") 153 | TenantId = $365tenant.TenantId 154 | InitialDomain = $365tenant.InitialDomain 155 | OrganizationID = $match.OrganizationID 156 | Licenses = $365tenant.Licenses 157 | LicensedUsers = $365tenant.LicensedUsers 158 | } 159 | $object = New-Object psobject -Property $hash 160 | $allMatches += $object 161 | } 162 | } 163 | } 164 | 165 | $uniqueMatches = $allMatches | Sort-Object key -Unique 166 | 167 | foreach ($match in $uniqueMatches) { 168 | $existingAssets = @() 169 | $existingAssets += GetAllITGItems -Resource "flexible_assets?filter[organization_id]=$($match.OrganizationID)&filter[flexible_asset_type_id]=$assetTypeID" 170 | $matchingAsset = $existingAssets | Where-Object {$_.attributes.traits.'tenant-id' -contains $match.TenantId} 171 | 172 | if ($matchingAsset) { 173 | Write-Host "Updating Office 365 tenant for $($match.tenantName)" 174 | $UpdatedBody = Build365TenantAsset -tenantInfo $match 175 | $updatedItem = UpdateITGItem -resource flexible_assets -existingItem $matchingAsset -newBody $UpdatedBody 176 | } 177 | else { 178 | Write-Host "Creating Office 365 tenant for $($match.tenantName)" 179 | $newBody = Build365TenantAsset -tenantInfo $match 180 | $newItem = CreateITGItem -resource flexible_assets -body $newBody 181 | } 182 | } -------------------------------------------------------------------------------- /Office365/General/Enable-AuditLogging/script1.ps1: -------------------------------------------------------------------------------- 1 | <# This script will connect to all delegated Office 365 tenants and check whether the Unified Audit Log is enabled. If it's not, it will create an Exchange admin user with a standard password. Once it's processed, you'll need to wait a few hours (preferably a day), then run the second script. The second script connects to your customers' Office 365 tenants via the new admin users and enables the Unified Audit Log ingestion. If successful, the second script will also remove the admin users created in this script. #> 2 | 3 | #------------------------------------------------------------- 4 | 5 | # Here are some things you can modify: 6 | 7 | # This is your partner admin user name that has delegated administration permission 8 | 9 | $UserName = "training@gcits.com" 10 | 11 | # IMPORTANT: This is the default password for the temporary admin users. Don't leave this as Password123, create a strong password between 8 and 16 characters containing Lowercase letters, Uppercase letters, Numbers and Symbols. 12 | 13 | $NewAdminPassword = "Password123" 14 | 15 | # IMPORTANT: This is the default User Principal Name prefix for the temporary admin users. Don't leave this as gcitsauditadmin, create something UNIQUE that DOESNT EXIST in any of your tenants already. If it exists, it'll be turned into an admin and then deleted. 16 | 17 | $NewAdminUserPrefix = "gcitsauditadmin" 18 | 19 | # This is the path for the exported CSVs. You can change this, though you'll need to make sure the path exists. This location is also referenced in the second script, so I recommend keeping it the same. 20 | 21 | $CreatedAdminsCsv = "C:\temp\CreatedAdmins.csv" 22 | 23 | $UALCustomersCsv = "C:\temp\UALCustomerStatus.csv" 24 | 25 | # Here's the end of the things you can modify. 26 | 27 | #------------------------------------------------------------- 28 | 29 | # This script block gets the Audit Log config settings 30 | 31 | $ScriptBlock = {Get-AdminAuditLogConfig} 32 | 33 | $Cred = get-credential -Credential $UserName 34 | 35 | # Connect to Azure Active Directory via Powershell 36 | 37 | Connect-MsolService -Credential $cred 38 | 39 | $Customers = Get-MsolPartnerContract -All 40 | 41 | $CompanyInfo = Get-MsolCompanyInformation 42 | 43 | Write-Host "Found $($Customers.Count) customers for $($CompanyInfo.DisplayName)" 44 | 45 | Write-Host " " 46 | Write-Host "----------------------------------------------------------" 47 | Write-Host " " 48 | 49 | foreach ($Customer in $Customers) { 50 | 51 | Write-Host $Customer.Name.ToUpper() 52 | Write-Host " " 53 | 54 | # Get license report 55 | 56 | Write-Host "Getting license report:" 57 | 58 | $CustomerLicenses = Get-MsolAccountSku -TenantId $Customer.TenantId 59 | 60 | foreach ($CustomerLicense in $CustomerLicenses) { 61 | 62 | Write-Host "$($Customer.Name) is reporting $($CustomerLicense.SkuPartNumber) with $($CustomerLicense.ActiveUnits) Active Units. They've assigned $($CustomerLicense.ConsumedUnits) of them." 63 | 64 | } 65 | 66 | if ($CustomerLicenses.Count -gt 0) { 67 | 68 | Write-Host " " 69 | 70 | # Get the initial domain for the customer. 71 | 72 | $InitialDomain = Get-MsolDomain -TenantId $Customer.TenantId | Where {$_.IsInitial -eq $true} 73 | 74 | # Construct the Exchange Online URL with the DelegatedOrg parameter. 75 | 76 | $DelegatedOrgURL = "https://ps.outlook.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name 77 | 78 | Write-Host "Getting UAL setting for $($InitialDomain.Name)" 79 | 80 | # Invoke-Command establishes a Windows PowerShell session based on the URL, 81 | # runs the command, and closes the Windows PowerShell session. 82 | 83 | $AuditLogConfig = Invoke-Command -ConnectionUri $DelegatedOrgURL -Credential $Cred -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection -ScriptBlock $ScriptBlock -HideComputerName 84 | 85 | Write-Host " " 86 | Write-Host "Audit Log Ingestion Enabled:" 87 | Write-Host $AuditLogConfig.UnifiedAuditLogIngestionEnabled 88 | 89 | # Check whether the Unified Audit Log is already enabled and log status in a CSV. 90 | 91 | if ($AuditLogConfig.UnifiedAuditLogIngestionEnabled) { 92 | 93 | $UALCustomerExport = @{ 94 | 95 | TenantId = $Customer.TenantId 96 | CompanyName = $Customer.Name 97 | DefaultDomainName = $Customer.DefaultDomainName 98 | UnifiedAuditLogIngestionEnabled = $AuditLogConfig.UnifiedAuditLogIngestionEnabled 99 | UnifiedAuditLogFirstOptInDate = $AuditLogConfig.UnifiedAuditLogFirstOptInDate 100 | DistinguishedName = $AuditLogConfig.DistinguishedName 101 | } 102 | 103 | $UALCustomersexport = @() 104 | 105 | $UALCustomersExport += New-Object psobject -Property $UALCustomerExport 106 | 107 | $UALCustomersExport | Select-Object TenantId, CompanyName, DefaultDomainName, UnifiedAuditLogIngestionEnabled, UnifiedAuditLogFirstOptInDate, DistinguishedName | Export-Csv -notypeinformation -Path $UALCustomersCSV -Append 108 | 109 | } 110 | 111 | # If the Unified Audit Log isn't enabled, log the status and create the admin user. 112 | 113 | if (!$AuditLogConfig.UnifiedAuditLogIngestionEnabled) { 114 | 115 | $UALDisabledCustomers += $Customer 116 | 117 | $UALCustomersExport = @() 118 | 119 | $UALCustomerExport = @{ 120 | 121 | TenantId = $Customer.TenantId 122 | CompanyName = $Customer.Name 123 | DefaultDomainName = $Customer.DefaultDomainName 124 | UnifiedAuditLogIngestionEnabled = $AuditLogConfig.UnifiedAuditLogIngestionEnabled 125 | UnifiedAuditLogFirstOptInDate = $AuditLogConfig.UnifiedAuditLogFirstOptInDate 126 | DistinguishedName = $AuditLogConfig.DistinguishedName 127 | } 128 | 129 | $UALCustomersExport += New-Object psobject -Property $UALCustomerExport 130 | $UALCustomersExport | Select-Object TenantId, CompanyName, DefaultDomainName, UnifiedAuditLogIngestionEnabled, UnifiedAuditLogFirstOptInDate, DistinguishedName | Export-Csv -notypeinformation -Path $UALCustomersCSV -Append 131 | 132 | 133 | # Build the User Principal Name for the new admin user 134 | 135 | $NewAdminUPN = -join ($NewAdminUserPrefix, "@", $($InitialDomain.Name)) 136 | 137 | Write-Host " " 138 | Write-Host "Audit Log isn't enabled for $($Customer.Name). Creating a user with UPN: $NewAdminUPN, assigning user to Company Administrators role." 139 | Write-Host "Adding $($Customer.Name) to CSV to enable UAL in second script." 140 | 141 | 142 | $secpasswd = ConvertTo-SecureString $NewAdminPassword -AsPlainText -Force 143 | $NewAdminCreds = New-Object System.Management.Automation.PSCredential ($NewAdminUPN, $secpasswd) 144 | 145 | New-MsolUser -TenantId $Customer.TenantId -DisplayName "Audit Admin" -UserPrincipalName $NewAdminUPN -Password $NewAdminPassword -ForceChangePassword $false 146 | 147 | Add-MsolRoleMember -TenantId $Customer.TenantId -RoleName "Company Administrator" -RoleMemberEmailAddress $NewAdminUPN 148 | 149 | $AdminProperties = @{ 150 | TenantId = $Customer.TenantId 151 | CompanyName = $Customer.Name 152 | DefaultDomainName = $Customer.DefaultDomainName 153 | UserPrincipalName = $NewAdminUPN 154 | Action = "ADDED" 155 | } 156 | 157 | $CreatedAdmins = @() 158 | $CreatedAdmins += New-Object psobject -Property $AdminProperties 159 | 160 | $CreatedAdmins | Select-Object TenantId, CompanyName, DefaultDomainName, UserPrincipalName, Action | Export-Csv -notypeinformation -Path $CreatedAdminsCsv -Append 161 | 162 | Write-Host " " 163 | 164 | } 165 | 166 | } 167 | 168 | Write-Host " " 169 | Write-Host "----------------------------------------------------------" 170 | Write-Host " " 171 | 172 | } 173 | 174 | Write-Host "Admin Creation Completed for tenants without Unified Audit Logging, please wait 12 hours before running the second script." 175 | 176 | 177 | Write-Host " " -------------------------------------------------------------------------------- /ITGlue/Office365Sync/auzreFunction.ps1: -------------------------------------------------------------------------------- 1 | Write-Output "PowerShell Timer trigger function executed at:$(get-date)"; 2 | 3 | $FunctionName = 'Sync365TenantsWithITGlue' 4 | $ModuleName = 'MSOnline' 5 | $ModuleVersion = '1.1.166.0' 6 | $username = $Env:user 7 | $pw = $Env:password 8 | #import PS module 9 | $PSModulePath = "D:\home\site\wwwroot\$FunctionName\bin\$ModuleName\$ModuleVersion\$ModuleName.psd1" 10 | $key = "ITGLUEAPIKEYGOESHERE" 11 | $assetTypeID = 99999 12 | $baseURI = "https://api.itglue.com" 13 | $headers = @{ 14 | "x-api-key" = $key 15 | } 16 | 17 | Import-module $PSModulePath 18 | 19 | # Build Credentials 20 | $keypath = "D:\home\site\wwwroot\$FunctionName\bin\keys\PassEncryptKey.key" 21 | $secpassword = $pw | ConvertTo-SecureString -Key (Get-Content $keypath) 22 | $credential = New-Object System.Management.Automation.PSCredential ($username, $secpassword) 23 | 24 | # Connect to MSOnline 25 | 26 | Connect-MsolService -Credential $credential 27 | 28 | function GetAllITGItems($Resource) { 29 | $array = @() 30 | 31 | $body = Invoke-RestMethod -Method get -Uri "$baseUri/$Resource" -Headers $headers -ContentType application/vnd.api+json 32 | $array += $body.data 33 | Write-Output "Retrieved $($array.Count) items" 34 | 35 | if ($body.links.next) { 36 | do { 37 | $body = Invoke-RestMethod -Method get -Uri $body.links.next -Headers $headers -ContentType application/vnd.api+json 38 | $array += $body.data 39 | Write-Output "Retrieved $($array.Count) items" 40 | } while ($body.links.next) 41 | } 42 | return $array 43 | } 44 | 45 | function CreateITGItem ($resource, $body) { 46 | $item = Invoke-RestMethod -Method POST -ContentType application/vnd.api+json -Uri $baseURI/$resource -Body $body -Headers $headers 47 | return $item 48 | } 49 | 50 | function UpdateITGItem ($resource, $existingItem, $newBody) { 51 | $updatedItem = Invoke-RestMethod -Method Patch -Uri "$baseUri/$Resource/$($existingItem.id)" -Headers $headers -ContentType application/vnd.api+json -Body $newBody 52 | return $updatedItem 53 | } 54 | 55 | function Build365TenantAsset ($tenantInfo) { 56 | 57 | $body = @{ 58 | data = @{ 59 | type = "flexible-assets" 60 | attributes = @{ 61 | "organization-id" = $tenantInfo.OrganizationID 62 | "flexible-asset-type-id" = $assettypeID 63 | traits = @{ 64 | "tenant-name" = $tenantInfo.TenantName 65 | "tenant-id" = $tenantInfo.TenantID 66 | "initial-domain" = $tenantInfo.InitialDomain 67 | "verified-domains" = $tenantInfo.Domains 68 | "licenses" = $tenantInfo.Licenses 69 | "licensed-users" = $tenantInfo.LicensedUsers 70 | } 71 | } 72 | } 73 | } 74 | 75 | $tenantAsset = $body | ConvertTo-Json -Depth 10 76 | return $tenantAsset 77 | } 78 | 79 | 80 | 81 | $customers = Get-MsolPartnerContract -All 82 | 83 | $365domains = @() 84 | 85 | foreach ($customer in $customers) { 86 | Write-Output "Getting domains for $($customer.name)" 87 | $companyInfo = Get-MsolCompanyInformation -TenantId $customer.TenantId 88 | 89 | $customerDomains = Get-MsolDomain -TenantId $customer.TenantId | Where-Object {$_.status -contains "Verified"} 90 | $initialDomain = $customerDomains | Where-Object {$_.isInitial} 91 | $Licenses = $null 92 | $licenseTable 93 | $Licenses = Get-MsolAccountSku -TenantId $customer.TenantId 94 | if ($Licenses) { 95 | $licenseTableTop = "
License NameActiveConsumedUnused
" 96 | $licenseTableBottom = "
" 97 | $licensesColl = @() 98 | foreach ($license in $licenses) { 99 | $licenseString = "$($license.SkuPartNumber)$($license.ActiveUnits) active$($license.ConsumedUnits) consumed$($license.ActiveUnits - $license.ConsumedUnits) unused" 100 | $licensesColl += $licenseString 101 | } 102 | if ($licensesColl) { 103 | $licenseString = $licensesColl -join "" 104 | } 105 | $licenseTable = "{0}{1}{2}" -f $licenseTableTop, $licenseString, $licenseTableBottom 106 | } 107 | 108 | $licensedUsers = $null 109 | $licensedUserTable = $null 110 | $licensedUsers = get-msoluser -TenantId $customer.TenantId -All | Where-Object {$_.islicensed} | Sort-Object UserPrincipalName 111 | if ($licensedUsers) { 112 | $licensedUsersTableTop = "
Display NameAddressesAssigned Licenses
" 113 | $licensedUsersTableBottom = "
" 114 | $licensedUserColl = @() 115 | foreach ($user in $licensedUsers) { 116 | 117 | $aliases = (($user.ProxyAddresses | Where-Object {$_ -cnotmatch "SMTP" -and $_ -notmatch ".onmicrosoft.com"}) -replace "SMTP:", " ") -join "
" 118 | $licensedUserString = "$($user.DisplayName)$($user.UserPrincipalName)
$aliases$(($user.Licenses.accountsku.skupartnumber) -join "
")" 119 | $licensedUserColl += $licensedUserString 120 | } 121 | if ($licensedUserColl) { 122 | $licensedUserString = $licensedUserColl -join "" 123 | } 124 | $licensedUserTable = "{0}{1}{2}" -f $licensedUsersTableTop, $licensedUserString, $licensedUsersTableBottom 125 | 126 | 127 | } 128 | 129 | 130 | $hash = [ordered]@{ 131 | TenantName = $companyInfo.displayname 132 | PartnerTenantName = $customer.name 133 | Domains = $customerDomains.name 134 | TenantId = $customer.TenantId 135 | InitialDomain = $initialDomain.name 136 | Licenses = $licenseTable 137 | LicensedUsers = $licensedUserTable 138 | } 139 | $object = New-Object psobject -Property $hash 140 | $365domains += $object 141 | 142 | } 143 | 144 | # Get all Contacts 145 | $itgcontacts = GetAllITGItems -Resource contacts 146 | 147 | $itgEmailRecords = @() 148 | foreach ($contact in $itgcontacts) { 149 | foreach ($email in $contact.attributes."contact-emails") { 150 | $hash = @{ 151 | Domain = ($email.value -split "@")[1] 152 | OrganizationID = $contact.attributes.'organization-id' 153 | } 154 | $object = New-Object psobject -Property $hash 155 | $itgEmailRecords += $object 156 | } 157 | } 158 | 159 | $allMatches = @() 160 | foreach ($365tenant in $365domains) { 161 | foreach ($domain in $365tenant.Domains) { 162 | $itgContactMatches = $itgEmailRecords | Where-Object {$_.domain -contains $domain} 163 | foreach ($match in $itgContactMatches) { 164 | $hash = [ordered]@{ 165 | Key = "$($365tenant.TenantId)-$($match.OrganizationID)" 166 | TenantName = $365tenant.TenantName 167 | Domains = ($365tenant.domains -join ", ") 168 | TenantId = $365tenant.TenantId 169 | InitialDomain = $365tenant.InitialDomain 170 | OrganizationID = $match.OrganizationID 171 | Licenses = $365tenant.Licenses 172 | LicensedUsers = $365tenant.LicensedUsers 173 | } 174 | $object = New-Object psobject -Property $hash 175 | $allMatches += $object 176 | } 177 | } 178 | } 179 | 180 | $uniqueMatches = $allMatches | Sort-Object key -Unique 181 | 182 | foreach ($match in $uniqueMatches) { 183 | $existingAssets = @() 184 | $existingAssets += GetAllITGItems -Resource "flexible_assets?filter[organization_id]=$($match.OrganizationID)&filter[flexible_asset_type_id]=$assetTypeID" 185 | $matchingAsset = $existingAssets | Where-Object {$_.attributes.traits.'tenant-id' -contains $match.TenantId} 186 | 187 | if ($matchingAsset) { 188 | Write-Output "Updating Office 365 tenant for $($match.tenantName)" 189 | $UpdatedBody = Build365TenantAsset -tenantInfo $match 190 | $updatedItem = UpdateITGItem -resource flexible_assets -existingItem $matchingAsset -newBody $UpdatedBody 191 | } 192 | else { 193 | Write-Output "Creating Office 365 tenant for $($match.tenantName)" 194 | $newBody = Build365TenantAsset -tenantInfo $match 195 | $newItem = CreateITGItem -resource flexible_assets -body $newBody 196 | } 197 | } -------------------------------------------------------------------------------- /ITGlue/OrganisationSharePointSync/single.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | This script will sync IT Glue organisations with a SharePoint list in the root sharepoint site (eg tenantname.sharepoint.com). 3 | The list is called 'ITGlue Org Register' 4 | It should be run on a schedule to keep the SharePoint list up to date. 5 | #> 6 | 7 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 8 | $key = "EnterYourITGlueAPIKeyHere" 9 | # Note that EU hosted IT Glue Tenants may need to update the below value to "https://api.eu.itglue.com" 10 | $ITGbaseURI = "https://api.itglue.com" 11 | $headers = @{ 12 | "x-api-key" = $key 13 | } 14 | $client_id = "EnterYourSharePointAppClientIDHere" 15 | $client_secret = "EnterYourSharePointAppClientSecretHere" 16 | $tenant_id = "EnterYourTenantIDHere" 17 | $graphBaseUri = "https://graph.microsoft.com/v1.0/" 18 | $siteid = "root" 19 | $ListName = "ITGlue Org Register" 20 | 21 | function Get-GCITSITGlueItem($Resource) { 22 | $array = @() 23 | $body = Invoke-RestMethod -Method get -Uri "$ITGbaseUri/$Resource" -Headers $headers -ContentType application/vnd.api+json 24 | $array += $body.data 25 | if ($body.links.next) { 26 | do { 27 | $body = Invoke-RestMethod -Method get -Uri $body.links.next -Headers $headers -ContentType application/vnd.api+json 28 | $array += $body.data 29 | } while ($body.links.next) 30 | } 31 | return $array 32 | } 33 | 34 | function New-GCITSSharePointColumn($Name, $Type, $Indexed) { 35 | $column = [ordered]@{ 36 | name = $Name 37 | indexed = $Indexed 38 | $Type = @{} 39 | } 40 | return $column 41 | } 42 | 43 | function New-GCITSSharePointList ($Name, $ColumnCollection) { 44 | $list = @{ 45 | displayName = $Name 46 | columns = $columnCollection 47 | } | Convertto-json -Depth 10 48 | 49 | $newList = Invoke-RestMethod ` 50 | -Uri "$graphBaseUri/sites/$siteid/lists/" ` 51 | -Headers $SPHeaders ` 52 | -ContentType "application/json" ` 53 | -Method POST -Body $list 54 | return $newList 55 | } 56 | 57 | function Remove-GCITSSharePointList ($ListId) { 58 | $removeList = Invoke-RestMethod ` 59 | -Uri "$graphBaseUri/sites/$siteid/lists/$ListId" ` 60 | -Headers $SPHeaders ` 61 | -ContentType "application/json" ` 62 | -Method DELETE 63 | return $removeList 64 | } 65 | 66 | function Remove-GCITSSharePointListItem ($ListId, $ItemId) { 67 | $removeItem = Invoke-RestMethod ` 68 | -Uri "$graphBaseUri/sites/$siteid/lists/$ListId/items/$ItemId" ` 69 | -Headers $SPHeaders ` 70 | -ContentType "application/json" ` 71 | -Method DELETE 72 | return $removeItem 73 | } 74 | 75 | function New-GCITSSharePointListItem($ItemObject, $ListId) { 76 | 77 | $itemBody = @{ 78 | fields = $ItemObject 79 | } | ConvertTo-Json -Depth 10 80 | 81 | $listItem = Invoke-RestMethod ` 82 | -Uri "$graphBaseUri/sites/$siteid/lists/$listId/items" ` 83 | -Headers $SPHeaders ` 84 | -ContentType "application/json" ` 85 | -Method Post ` 86 | -Body $itemBody 87 | } 88 | 89 | function Get-GCITSSharePointItem($ListId, $ItemId) { 90 | 91 | if ($ItemId) { 92 | $listItem = Invoke-RestMethod -Uri $graphBaseUri/sites/$siteid/lists/$listId/items/$ItemId ` 93 | -Method Get -headers $SPHeaders ` 94 | -ContentType application/json 95 | $value = $listItem 96 | } 97 | else { 98 | $listItems = $null 99 | $listItems = Invoke-RestMethod -Uri $graphBaseUri/sites/$siteid/lists/$listId/items?expand=fields ` 100 | -Method Get -headers $SPHeaders ` 101 | -ContentType application/json 102 | $value = @() 103 | $value = $listItems.value 104 | if ($listitems."@odata.nextLink") { 105 | $nextLink = $true 106 | } 107 | if ($nextLink) { 108 | do { 109 | $listItems = Invoke-RestMethod -Uri $listitems."@odata.nextLink"` 110 | -Method Get -headers $SPHeaders ` 111 | -ContentType application/json 112 | $value += $listItems.value 113 | if (!$listitems."@odata.nextLink") { 114 | $nextLink = $false 115 | } 116 | } until (!$nextLink) 117 | } 118 | } 119 | return $value 120 | } 121 | 122 | function Set-GCITSSharePointListItem($ListId, $ItemId, $ItemObject) { 123 | $listItem = Invoke-RestMethod -Uri $graphBaseUri/sites/$siteid/lists/$listId/items/$ItemId/fields ` 124 | -Method Patch -headers $SPHeaders ` 125 | -ContentType application/json ` 126 | -Body ($itemObject | ConvertTo-Json) 127 | $return = $listItem 128 | } 129 | 130 | function Get-GCITSAccessToken { 131 | $authority = "https://login.microsoftonline.com/$tenant_id" 132 | $tokenEndpointUri = "$authority/oauth2/token" 133 | $resource = "https://graph.microsoft.com" 134 | $content = "grant_type=client_credentials&client_id=$client_id&client_secret=$client_secret&resource=$resource" 135 | $graphBaseUri = "https://graph.microsoft.com/v1.0" 136 | $response = Invoke-RestMethod -Uri $tokenEndpointUri -Body $content -Method Post -UseBasicParsing 137 | $access_token = $response.access_token 138 | return $access_token 139 | } 140 | 141 | function Get-GCITSMSGraphResource($Resource) { 142 | $graphBaseUri = "https://graph.microsoft.com/v1.0" 143 | $values = @() 144 | $result = Invoke-RestMethod -Uri "$graphBaseUri/$resource" -Headers $headers 145 | $values += $result.value 146 | if ($result.'@odata.nextLink') { 147 | do { 148 | $result = Invoke-RestMethod -Uri $result.'@odata.nextLink' -Headers $headers 149 | $values += $result.value 150 | } while ($result.'@odata.nextLink') 151 | } 152 | return $values 153 | } 154 | 155 | function Get-GCITSSharePointList($ListName) { 156 | $list = Invoke-RestMethod ` 157 | -Uri "$graphBaseUri/sites/$siteid/lists?expand=columns&`$filter=displayName eq '$ListName'" ` 158 | -Headers $SPHeaders ` 159 | -ContentType "application/json" ` 160 | -Method GET 161 | $list = $list.value 162 | return $list 163 | } 164 | 165 | $organisations = Get-GCITSITGlueItem -Resource organizations 166 | 167 | $access_token = Get-GCITSAccessToken 168 | $SPHeaders = @{Authorization = "Bearer $access_token"} 169 | 170 | $list = Get-GCITSSharePointList -ListName $ListName 171 | 172 | if (!$list) { 173 | Write-Output "List not found, creating List" 174 | # Initiate Columns 175 | $columnCollection = @() 176 | $columnCollection += New-GCITSSharePointColumn -Name ShortName -Type text -Indexed $true 177 | $columnCollection += New-GCITSSharePointColumn -Name ITGlueID -Type number -Indexed $true 178 | 179 | $List = New-GCITSSharePointList -Name $ListName -ColumnCollection $columnCollection 180 | } 181 | else { 182 | Write-Output "List Exists, retrieving existing items" 183 | $existingItems = Get-GCITSSharePointItem -ListId $list.id 184 | Write-Output "Retrieved $($existingItems.count) existing items" 185 | } 186 | 187 | foreach ($organisation in $organisations) { 188 | Write-Output "Checking $($organisation.attributes.Name)" 189 | $existingitem = $existingItems | Where-Object {$_.fields.ITGlueID -contains $organisation.id} 190 | 191 | # if there is no match in SharePoint for the existing org, create the item 192 | if (!$existingitem) { 193 | $item = @{ 194 | "Title" = $organisation.attributes.name 195 | "ShortName" = $organisation.attributes.'short-name' 196 | "ITGlueID" = $organisation.id 197 | } 198 | Write-Output "Creating $($organisation.attributes.Name)" 199 | New-GCITSSharePointListItem -ListId $list.id -ItemObject $item 200 | } 201 | else { 202 | if ($existingitem.fields.Title -notcontains $organisation.attributes.name ` 203 | -or $existingitem.fields.ShortName -notcontains $organisation.attributes.'short-name') { 204 | Write-Output "Updating $($organisation.attributes.Name)" 205 | $item = @{ 206 | "Title" = $organisation.attributes.name 207 | "ShortName" = $organisation.attributes.'short-name' 208 | "ITGlueID" = $organisation.id 209 | } 210 | Set-GCITSSharePointListItem -ListId $list.Id -ItemId $existingitem.id -ItemObject $item 211 | } 212 | } 213 | } 214 | Write-Output "Cleaning up" 215 | 216 | foreach ($existingItem in $existingItems) { 217 | 218 | if ($organisations.id -notcontains $existingitem.fields.itglueid) { 219 | Write-Output "Couldn't resolve, removing $($existingItem.fields.title)" 220 | $removeItem = Remove-GCITSSharePointListItem -ListId $listId -ItemId $existingitem.id 221 | } 222 | 223 | } -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Export-UserExternalMailForwarding/README.md: -------------------------------------------------------------------------------- 1 | [Source](https://gcits.com/knowledge-base/find-inbox-rules-forward-mail-externally-office-365-powershell/ "Permalink to Find all Inbox Rules that forward mail externally from Office 365 with PowerShell") 2 | 3 | # Find all Inbox Rules that forward mail externally from Office 365 with PowerShell 4 | 5 | ![Find Office 365 Inbox Rules that forward externally][1] 6 | 7 | It's a good idea to be aware of any mailbox level Inbox Rules that automatically forward mail outside of your organisation. Microsoft recommends that these types of rules be disabled by admins by default. Stopping mail from being auto-forwarded even counts towards your Office 365 Secure Score. 8 | 9 | Auto-forwarding mail to external contacts can have some legitimate use cases, however it can also be used by hackers and rogue employees to exfiltrate data from your organisation. You can disable this functionality for own tenant and your Office 365 Customers [using the PowerShell scripts here.][2] 10 | 11 | Whether you're going to disable this functionality or not, it's worth checking which users in your organisation are automatically forwarding mail outside of the company. If you do choose to disable this functionality, you should first check to see whether you need to add any exceptions for legitimate rules. 12 | 13 | The following scripts will check all mailboxes for these sorts of inbox rules and export details about the rule and the external contacts to a CSV. 14 | 15 | The first script is designed to be run on a single Office 365 tenant. The second script is for Microsoft Partners, who can use it to check for these types of Inbox rules on all users in all customer environments. 16 | 17 | ## How to run these scripts 18 | 19 | - Double click on either of the scripts below to select it all 20 | - Copy and paste it into Visual Studio Code and save it as a **.ps1** file 21 | - Run it by pressing **F5** 22 | - Enter the credentials of an Office 365 global admin, Exchange admin or delegated administrator 23 | - Wait for the script to complete. If you're running this across a number of tenants, you'll probably be waiting a while. ![Running The Inbox Forwarding Rule PowerShell Script][3] 24 | - A CSV of it's results will be saved to C:tempexternalrules.csv or C:tempcustomerexternalrules.csv as it processes.![Exported CSV Of Externally Forwarding Inbox Rules][4] 25 | 26 | ## How to check for Inbox Rules that forward externally in a single Office 365 tenant using PowerShell 27 | 28 | ```powershell 29 | Function Connect-EXOnline { 30 | $credentials = Get-Credential 31 | Write-Output "Getting the Exchange Online cmdlets" 32 | $Session = New-PSSession -ConnectionUri https://outlook.office365.com/powershell-liveid/ ` 33 | -ConfigurationName Microsoft.Exchange -Credential $credentials ` 34 | -Authentication Basic -AllowRedirection 35 | Import-PSSession $Session 36 | } 37 | 38 | Connect-EXOnline 39 | $domains = Get-AcceptedDomain 40 | $mailboxes = Get-Mailbox -ResultSize Unlimited 41 | 42 | foreach ($mailbox in $mailboxes) { 43 | 44 | $forwardingRules = $null 45 | Write-Host "Checking rules for $($mailbox.displayname) - $($mailbox.primarysmtpaddress)" -foregroundColor Green 46 | $rules = get-inboxrule -Mailbox $mailbox.primarysmtpaddress 47 | 48 | $forwardingRules = $rules | Where-Object {$_.forwardto -or $_.forwardasattachmentto} 49 | 50 | foreach ($rule in $forwardingRules) { 51 | $recipients = @() 52 | $recipients = $rule.ForwardTo | Where-Object {$_ -match "SMTP"} 53 | $recipients += $rule.ForwardAsAttachmentTo | Where-Object {$_ -match "SMTP"} 54 | 55 | $externalRecipients = @() 56 | 57 | foreach ($recipient in $recipients) { 58 | $email = ($recipient -split "SMTP:")[1].Trim("]") 59 | $domain = ($email -split "@")[1] 60 | 61 | if ($domains.DomainName -notcontains $domain) { 62 | $externalRecipients += $email 63 | } 64 | } 65 | 66 | if ($externalRecipients) { 67 | $extRecString = $externalRecipients -join ", " 68 | Write-Host "$($rule.Name) forwards to $extRecString" -ForegroundColor Yellow 69 | 70 | $ruleHash = $null 71 | $ruleHash = [ordered]@{ 72 | PrimarySmtpAddress = $mailbox.PrimarySmtpAddress 73 | DisplayName = $mailbox.DisplayName 74 | RuleId = $rule.Identity 75 | RuleName = $rule.Name 76 | RuleDescription = $rule.Description 77 | ExternalRecipients = $extRecString 78 | } 79 | $ruleObject = New-Object PSObject -Property $ruleHash 80 | $ruleObject | Export-Csv C:tempexternalrules.csv -NoTypeInformation -Append 81 | } 82 | } 83 | } 84 | ``` 85 | 86 | ## How to check for Inbox Rules that forward externally in all customer Office 365 tenants using PowerShell 87 | 88 | ```powershell 89 | $credential = Get-Credential 90 | Connect-MsolService -Credential $credential 91 | $customers = Get-msolpartnercontract 92 | foreach ($customer in $customers) { 93 | 94 | $InitialDomain = Get-MsolDomain -TenantId $customer.TenantId | Where-Object {$_.IsInitial -eq $true} 95 | 96 | Write-Host "Checking $($customer.Name)" 97 | $DelegatedOrgURL = "https://outlook.office365.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name 98 | $s = New-PSSession -ConnectionUri $DelegatedOrgURL -Credential $credential -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection 99 | Import-PSSession $s -CommandName Get-Mailbox, Get-InboxRule, Get-AcceptedDomain -AllowClobber 100 | $mailboxes = $null 101 | $mailboxes = Get-Mailbox -ResultSize Unlimited 102 | $domains = Get-AcceptedDomain 103 | 104 | foreach ($mailbox in $mailboxes) { 105 | 106 | $forwardingRules = $null 107 | 108 | Write-Host "Checking rules for $($mailbox.displayname) - $($mailbox.primarysmtpaddress)" 109 | $rules = get-inboxrule -Mailbox $mailbox.primarysmtpaddress 110 | $forwardingRules = $rules | Where-Object {$_.forwardto -or $_.forwardasattachmentto} 111 | 112 | foreach ($rule in $forwardingRules) { 113 | $recipients = @() 114 | $recipients = $rule.ForwardTo | Where-Object {$_ -match "SMTP"} 115 | $recipients += $rule.ForwardAsAttachmentTo | Where-Object {$_ -match "SMTP"} 116 | $externalRecipients = @() 117 | 118 | foreach ($recipient in $recipients) { 119 | $email = ($recipient -split "SMTP:")[1].Trim("]") 120 | $domain = ($email -split "@")[1] 121 | 122 | if ($domains.DomainName -notcontains $domain) { 123 | $externalRecipients += $email 124 | } 125 | } 126 | 127 | if ($externalRecipients) { 128 | $extRecString = $externalRecipients -join ", " 129 | Write-Host "$($rule.Name) forwards to $extRecString" -ForegroundColor Yellow 130 | 131 | $ruleHash = $null 132 | $ruleHash = [ordered]@{ 133 | Customer = $customer.Name 134 | TenantId = $customer.TenantId 135 | PrimarySmtpAddress = $mailbox.PrimarySmtpAddress 136 | DisplayName = $mailbox.DisplayName 137 | RuleId = $rule.Identity 138 | RuleName = $rule.Name 139 | RuleDescription = $rule.Description 140 | ExternalRecipients = $extRecString 141 | } 142 | $ruleObject = New-Object PSObject -Property $ruleHash 143 | $ruleObject | Export-Csv C:tempcustomerExternalRules.csv -NoTypeInformation -Append 144 | } 145 | } 146 | } 147 | } 148 | ``` 149 | 150 | ### About The Author 151 | 152 | ![Elliot Munro][5] 153 | 154 | #### [ Elliot Munro ][6] 155 | 156 | Elliot Munro is an Office 365 MCSA from the Gold Coast, Australia supporting hundreds of small businesses with GCITS. If you have an Office 365 or Azure issue that you'd like us to take a look at (or have a request for a useful script) send Elliot an email at [elliot@gcits.com][7] 157 | 158 | [1]: https://gcits.com/wp-content/uploads/RemoveUnnecessaryLicensesOffice365SharedMailbox-1030x436.png 159 | [2]: https://gcits.com/knowledge-base/block-inbox-rules-forwarding-mail-externally-office-365-using-powershell/ 160 | [3]: https://gcits.com/wp-content/uploads/RunningTheInboxForwardingRulePowerShellScript-1030x97.png 161 | [4]: https://gcits.com/wp-content/uploads/ExportedCSVOfExternallyForwardingInboxRules-1030x383.png 162 | [5]: https://gcits.com/wp-content/uploads/AAEAAQAAAAAAAA2QAAAAJDNlN2NmM2Y4LTU5YWYtNGRiNC1hMmI2LTBhMzdhZDVmNWUzNA-80x80.jpg 163 | [6]: https://gcits.com/author/elliotmunro/ 164 | [7]: mailto:elliot%40gcits.com 165 | -------------------------------------------------------------------------------- /Office365/ExchangeOnline/Block-ExternalMailForwarding/README.md: -------------------------------------------------------------------------------- 1 | [Source](https://gcits.com/knowledge-base/block-inbox-rules-forwarding-mail-externally-office-365-using-powershell/ "Permalink to Block Inbox Rules from forwarding mail externally in Office 365 using PowerShell") 2 | 3 | # Block Inbox Rules from forwarding mail externally in Office 365 using PowerShell 4 | 5 | ![Block Inbox Rules from forwarding mail externally in Office 365 using PowerShell][1] 6 | 7 | Auto-forwarding inbox rules can be used by hackers and rogue employees to exfiltrate data from your organisation. Microsoft recommends that you disable this functionality by default using an Exchange transport rule. 8 | 9 | This article will show you how to script the creation of this Exchange transport rule in your own, and your customers', Office 365 tenants. 10 | 11 | Note: if you'd prefer not to script this, Microsoft give you an easy way to do this for a single Office 365 organisation. You can log into the [Office 365 Security and Compliance Center][2] and click on your Office 365 secure score recommendations. One of the recommendations is to prevent these types of rules from sending data externally, there's a button to apply the fix, which creates an Exchange transport rule for you. 12 | 13 | ## What if I need to add exceptions 14 | 15 | Before you add this rule, I recommend seeing which Inbox Rules will be affected in your own, or your customers', Office 365 environments. [You can use our scripts here][3] to detect Inbox Rules that forward externally. Once it completes, you can use the exported CSV to define which exceptions you'll need to add to the Exchange transport rule once it's set up. 16 | 17 | ### How to use the scripts 18 | 19 | 1. Double click on one of the scripts below to select it all 20 | 2. Copy and paste it into Visual Studio Code and save it as a **.ps1** file 21 | 3. Run it by pressing **F5** 22 | 4. Enter the credentials of your Office 365 global admin, Exchange admin or delegated administrator 23 | 5. Wait for the script to process. 24 | 25 | ## Block Inbox Rules from forwarding mail externally in your own Office 365 tenant using PowerShell 26 | 27 | If you'd prefer not to add the rule using the Security and Compliance Center, you can run the following script via PowerShell. 28 | 29 | ```powershell 30 | Function Connect-EXOnline { 31 | $credentials = Get-Credential 32 | Write-Output "Getting the Exchange Online cmdlets" 33 | $session = New-PSSession -ConnectionUri https://outlook.office365.com/powershell-liveid/ `-ConfigurationName Microsoft.Exchange -Credential $credentials` 34 | -Authentication Basic -AllowRedirection 35 | Import-PSSession $session 36 | } 37 | Connect-EXOnline 38 | 39 | $externalTransportRuleName = "Inbox Rules To External Block" 40 | $rejectMessageText = "To improve security, auto-forwarding rules to external addresses has been disabled. Please contact your Microsoft Partner if you'd like to set up an exception." 41 | 42 | $externalForwardRule = Get-TransportRule | Where-Object {$\_.Identity -contains $externalTransportRuleName} 43 | 44 | if (!$externalForwardRule) { 45 | Write-Output "Client Rules To External Block not found, creating Rule" 46 | New-TransportRule -name "Client Rules To External Block" -Priority 1 -SentToScope NotInOrganization -FromScope InOrganization -MessageTypeMatches AutoForward -RejectMessageEnhancedStatusCode 5.7.1 -RejectMessageReasonText $rejectMessageText 47 | } 48 | ``` 49 | 50 | ## Block Inbox Rules from forwarding mail externally in all customer Office 365 tenants 51 | 52 | Instead of manually logging into the Security and Compliance Center for each of your customers' Office 365 tenants, you can use the following script to create the Exchange Transport Rules for each of your customers. 53 | 54 | ```powershell 55 | $credential = Get-Credential 56 | Connect-MsolService -Credential $credential 57 | $customers = Get-MsolPartnerContract -All 58 | $externalTransportRuleName = "Inbox Rules To External Block" 59 | $rejectMessageText = "To improve security, auto-forwarding rules to external addresses has been disabled. Please contact your Microsoft Partner if you'd like to set up an exception." 60 | 61 | Write-Output "Found $($customers.Count) customers for $((Get-MsolCompanyInformation).displayname)." 62 | 63 | foreach ($customer in $customers) { 64 | $InitialDomain = Get-MsolDomain -TenantId $customer.TenantId | Where-Object {$_.IsInitial -eq $true} 65 | 66 | Write-Output "Checking transport rule for $($Customer.Name)" 67 | $DelegatedOrgURL = "https://outlook.office365.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name 68 | $s = New-PSSession -ConnectionUri $DelegatedOrgURL -Credential $credential -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection 69 | Import-PSSession $s -CommandName Get-TransportRule, New-TransportRule, Set-TransportRule -AllowClobber 70 | 71 | $externalForwardRule = Get-TransportRule | Where-Object {$_.Identity -contains $externalTransportRuleName} 72 | 73 | if (!$externalForwardRule) { 74 | Write-Output "Client Rules To External Block not found, creating Rule" 75 | New-TransportRule -name "Client Rules To External Block" -Priority 1 -SentToScope NotInOrganization -FromScope InOrganization -MessageTypeMatches AutoForward -RejectMessageEnhancedStatusCode 5.7.1 -RejectMessageReasonText $rejectMessageText 76 | } 77 | Remove-PSSession $s 78 | } 79 | ``` 80 | 81 | ## Create an Azure function to block externally fowarding inbox rules on all Office 365 customers 82 | 83 | Rather than running this script each time you add a new Office 365 customer, you can set it up to run in an Azure Function, so that it's continuously checking your customers for the presence of the transport rule, and creating it if it doesn't exist. Here's how you can do this yourself. 84 | 85 | [Follow this guide][4] to set up an Azure Function app via the Azure Portal, and secure the credentials of your Office 365 delegated admin. 86 | 87 | - Call it something like **BlockExternalForwardingFromInboxRules** (If you're calling it something different, update the script below.) 88 | - Make sure you've saved and uploaded the MSOnline PowerShell module and keys as per the guide. 89 | - Set it to run on a timer of your choosing. We're using the following cron trigger: 90 | 0 0 10 \* \* \* 91 | 92 | ![Set Up Another Timer Trigger In Azure Functions][5] 93 | 94 | - Copy and paste the code below into your new Azure Function. 95 | - Click **Save and run** to start the function and confirm that it works. 96 | 97 | ```powershell 98 | Write-Output "PowerShell Timer trigger function executed at:$(get-date)"; 99 | 100 | $FunctionName = 'BlockExternalForwardingFromInboxRules' 101 | $ModuleName = 'MSOnline' 102 | $ModuleVersion = '1.1.166.0' 103 | $username = $Env:user 104 | $pw = $Env:password 105 | #import PS module 106 | $PSModulePath = "D:homesitewwwroot$FunctionNamebin$ModuleName$ModuleVersion$ModuleName.psd1" 107 | $res = "D:homesitewwwroot$FunctionNamebin" 108 | 109 | Import-module $PSModulePath 110 | 111 | # Build Credentials 112 | $keypath = "D:homesitewwwroot$FunctionNamebinkeysPassEncryptKey.key" 113 | $secpassword = $pw | ConvertTo-SecureString -Key (Get-Content $keypath) 114 | $credential = New-Object System.Management.Automation.PSCredential ($username, $secpassword) 115 | 116 | # Connect to MSOnline 117 | Connect-MsolService -Credential $credential 118 | $customers = @() 119 | 120 | Write-Output "Found $($customers.Count) customers for $((Get-MsolCompanyInformation).displayname)." 121 | 122 | $externalTransportRuleName = "Inbox Rules To External Block" 123 | $rejectMessageText = "To improve security, auto-forwarding rules to external addresses has been disabled. Please contact your Microsoft Partner if you'd like to set up an exception." 124 | 125 | foreach ($customer in $customers) { 126 | $InitialDomain = Get-MsolDomain -TenantId $customer.TenantId | Where-Object {$_.IsInitial -eq $true} 127 | 128 | Write-Output "Checking transport rule for $($Customer.Name)" 129 | $DelegatedOrgURL = "https://outlook.office365.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name 130 | $s = New-PSSession -ConnectionUri $DelegatedOrgURL -Credential $credential -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection 131 | Import-PSSession $s -CommandName Get-Mailbox, Get-TransportRule, New-TransportRule, Set-TransportRule -AllowClobber 132 | 133 | $externalForwardRule = Get-TransportRule | Where-Object {$_.Identity -contains $externalTransportRuleName} 134 | 135 | if (!$externalForwardRule) { 136 | Write-Output "Client Rules To External Block not found, creating Rule" 137 | New-TransportRule -name "Client Rules To External Block" -Priority 1 -SentToScope NotInOrganization -FromScope InOrganization -MessageTypeMatches AutoForward -RejectMessageEnhancedStatusCode 5.7.1 -RejectMessageReasonText $rejectMessageText 138 | } 139 | 140 | Remove-PSSession $s 141 | } 142 | ``` 143 | 144 | ### About The Author 145 | 146 | ![Elliot Munro][6] 147 | 148 | #### [ Elliot Munro ][7] 149 | 150 | Elliot Munro is an Office 365 MCSA from the Gold Coast, Australia supporting hundreds of small businesses with GCITS. If you have an Office 365 or Azure issue that you'd like us to take a look at (or have a request for a useful script) send Elliot an email at [elliot@gcits.com][8] 151 | 152 | [1]: https://gcits.com/wp-content/uploads/RemoveUnnecessaryLicensesOffice365SharedMailbox-1030x436.png 153 | [2]: https://protection.office.com 154 | [3]: https://gcits.com/knowledge-base/find-inbox-rules-forward-mail-externally-office-365-powershell/ 155 | [4]: https://gcits.com/knowledge-base/connect-azure-function-office-365/ 156 | [5]: https://gcits.com/wp-content/uploads/SetUpAnotherTimerTriggerInAzureFunctions.png 157 | [6]: https://gcits.com/wp-content/uploads/AAEAAQAAAAAAAA2QAAAAJDNlN2NmM2Y4LTU5YWYtNGRiNC1hMmI2LTBhMzdhZDVmNWUzNA-80x80.jpg 158 | [7]: https://gcits.com/author/elliotmunro/ 159 | [8]: mailto:elliot%40gcits.com 160 | -------------------------------------------------------------------------------- /Office365/SharePoint/Enable-SharePointGraphApplication/single.ps1: -------------------------------------------------------------------------------- 1 | # This script needs to be run by an admin account in your Office 365 tenant 2 | # This script will create a SharePoint app in your organisation 3 | # It will export information about the application to a CSV located at C:\temp\. 4 | # The CSV will include the Client ID and Secret of the application, so keep it safe. 5 | 6 | # Confirm C:\temp exists 7 | $temp = Test-Path -Path C:\temp 8 | if ($temp) { 9 | #Write-Host "Path exists" 10 | } 11 | else { 12 | Write-Host "Creating Temp folder" 13 | New-Item -Path C:\temp -ItemType directory 14 | } 15 | 16 | $applicationName = "GCITS SharePoint Application" 17 | 18 | # Change this to true if you would like to overwrite any existing applications with matching names. 19 | $removeExistingAppWithSameName = $true 20 | # Modify the homePage, appIdURI and logoutURI values to whatever valid URI you like. They don't need to be actual addresses. 21 | $homePage = "https://secure.gcits.com" 22 | $appIdURI = "https://secure.gcits.com/$((New-Guid).ToString())" 23 | $logoutURI = "https://portal.office.com" 24 | 25 | $URIForApplicationPermissionCall = "https://graph.microsoft.com/v1.0/sites/root" 26 | $ApplicationPermissions = "Sites.Manage.All" 27 | 28 | Function Add-ResourcePermission($requiredAccess, $exposedPermissions, $requiredAccesses, $permissionType) { 29 | foreach ($permission in $requiredAccesses.Trim().Split(" ")) { 30 | $reqPermission = $null 31 | $reqPermission = $exposedPermissions | Where-Object {$_.Value -contains $permission} 32 | Write-Host "Collected information for $($reqPermission.Value) of type $permissionType" -ForegroundColor Green 33 | $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess 34 | $resourceAccess.Type = $permissionType 35 | $resourceAccess.Id = $reqPermission.Id 36 | $requiredAccess.ResourceAccess.Add($resourceAccess) 37 | } 38 | } 39 | 40 | Function Get-RequiredPermissions($requiredDelegatedPermissions, $requiredApplicationPermissions, $reqsp) { 41 | $sp = $reqsp 42 | $appid = $sp.AppId 43 | $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess 44 | $requiredAccess.ResourceAppId = $appid 45 | $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess] 46 | if ($requiredDelegatedPermissions) { 47 | Add-ResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope" 48 | } 49 | if ($requiredApplicationPermissions) { 50 | Add-ResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role" 51 | } 52 | return $requiredAccess 53 | } 54 | Function New-AppKey ($fromDate, $durationInYears, $pw) { 55 | $endDate = $fromDate.AddYears($durationInYears) 56 | $keyId = (New-Guid).ToString() 57 | $key = New-Object Microsoft.Open.AzureAD.Model.PasswordCredential($null, $endDate, $keyId, $fromDate, $pw) 58 | return $key 59 | } 60 | 61 | Function Test-AppKey($fromDate, $durationInYears, $pw) { 62 | 63 | $testKey = New-AppKey -fromDate $fromDate -durationInYears $durationInYears -pw $pw 64 | while ($testKey.Value -match "\+" -or $testKey.Value -match "/") { 65 | Write-Host "Secret contains + or / and may not authenticate correctly. Regenerating..." -ForegroundColor Yellow 66 | $pw = Initialize-AppKey 67 | $testKey = New-AppKey -fromDate $fromDate -durationInYears $durationInYears -pw $pw 68 | } 69 | Write-Host "Secret doesn't contain + or /. Continuing..." -ForegroundColor Green 70 | $key = $testKey 71 | 72 | return $key 73 | } 74 | 75 | Function Initialize-AppKey { 76 | $aesManaged = New-Object "System.Security.Cryptography.AesManaged" 77 | $aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC 78 | $aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros 79 | $aesManaged.BlockSize = 128 80 | $aesManaged.KeySize = 256 81 | $aesManaged.GenerateKey() 82 | return [System.Convert]::ToBase64String($aesManaged.Key) 83 | } 84 | function Confirm-MicrosoftGraphServicePrincipal { 85 | $graphsp = Get-AzureADServicePrincipal -SearchString "Microsoft Graph" 86 | if (!$graphsp) { 87 | $graphsp = Get-AzureADServicePrincipal -SearchString "Microsoft.Azure.AgregatorService" 88 | } 89 | if (!$graphsp) { 90 | Login-AzureRmAccount -Credential $credentials 91 | New-AzureRmADServicePrincipal -ApplicationId "00000003-0000-0000-c000-000000000000" 92 | $graphsp = Get-AzureADServicePrincipal -SearchString "Microsoft Graph" 93 | } 94 | return $graphsp 95 | } 96 | Write-Host "Connecting to Azure AD. The login window may appear behind Visual Studio Code." 97 | Connect-AzureAD 98 | 99 | Write-Host "Creating application in tenant: $((Get-AzureADTenantDetail).displayName)" 100 | 101 | # Check for the Microsoft Graph Service Principal. If it doesn't exist already, create it. 102 | $graphsp = Confirm-MicrosoftGraphServicePrincipal 103 | 104 | $existingapp = $null 105 | $existingapp = get-azureadapplication -SearchString $applicationName 106 | if ($existingapp -and $removeExistingAppWithSameName) { 107 | Remove-Azureadapplication -ObjectId $existingApp.objectId 108 | } 109 | 110 | # RSPS 111 | $rsps = @() 112 | if ($graphsp) { 113 | $rsps += $graphsp 114 | $tenant_id = (Get-AzureADTenantDetail).ObjectId 115 | $tenantName = (Get-AzureADTenantDetail).DisplayName 116 | 117 | # Add Required Resources Access (Microsoft Graph) 118 | $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess] 119 | $microsoftGraphRequiredPermissions = Get-RequiredPermissions -reqsp $graphsp -requiredApplicationPermissions $ApplicationPermissions -requiredDelegatedPermissions $DelegatedPermissions 120 | $requiredResourcesAccess.Add($microsoftGraphRequiredPermissions) 121 | 122 | # Get an application key 123 | $pw = Initialize-AppKey 124 | $fromDate = [System.DateTime]::Now 125 | $appKey = Test-AppKey -fromDate $fromDate -durationInYears 99 -pw $pw 126 | 127 | Write-Host "Creating the AAD application $applicationName" -ForegroundColor Blue 128 | $aadApplication = New-AzureADApplication -DisplayName $applicationName ` 129 | -HomePage $homePage ` 130 | -ReplyUrls $homePage ` 131 | -IdentifierUris $appIdURI ` 132 | -LogoutUrl $logoutURI ` 133 | -RequiredResourceAccess $requiredResourcesAccess ` 134 | -PasswordCredentials $appKey 135 | 136 | # Creating the Service Principal for the application 137 | $servicePrincipal = New-AzureADServicePrincipal -AppId $aadApplication.AppId 138 | 139 | Write-Host "Assigning Permissions" -ForegroundColor Yellow 140 | 141 | # Assign application permissions to the application 142 | foreach ($app in $requiredResourcesAccess) { 143 | $reqAppSP = $rsps | Where-Object {$_.appid -contains $app.ResourceAppId} 144 | Write-Host "Assigning Application permissions for $($reqAppSP.displayName)" -ForegroundColor DarkYellow 145 | foreach ($resource in $app.ResourceAccess) { 146 | if ($resource.Type -match "Role") { 147 | New-AzureADServiceAppRoleAssignment -ObjectId $serviceprincipal.ObjectId ` 148 | -PrincipalId $serviceprincipal.ObjectId -ResourceId $reqAppSP.ObjectId -Id $resource.Id 149 | } 150 | } 151 | } 152 | 153 | Write-Host "App Created" -ForegroundColor Green 154 | 155 | # Define parameters for Microsoft Graph access token retrieval 156 | $client_id = $aadApplication.AppId; 157 | $client_secret = $appkey.Value 158 | $tenant_id = (Get-AzureADTenantDetail).ObjectId 159 | $resource = "https://graph.microsoft.com" 160 | $authority = "https://login.microsoftonline.com/$tenant_id" 161 | $tokenEndpointUri = "$authority/oauth2/token" 162 | 163 | # Get the access token using grant type password for Delegated Permissions or grant type client_credentials for Application Permissions 164 | 165 | $content = "grant_type=client_credentials&client_id=$client_id&client_secret=$client_secret&resource=$resource" 166 | 167 | # Try to execute the API call 6 times 168 | 169 | $Stoploop = $false 170 | [int]$Retrycount = "0" 171 | do { 172 | try { 173 | $response = Invoke-RestMethod -Uri $tokenEndpointUri -Body $content -Method Post -UseBasicParsing 174 | Write-Host "Retrieved Access Token" -ForegroundColor Green 175 | # Assign access token 176 | $access_token = $response.access_token 177 | $body = $null 178 | 179 | $body = Invoke-RestMethod ` 180 | -Uri $UriForApplicationPermissionCall ` 181 | -Headers @{"Authorization" = "Bearer $access_token"} ` 182 | -ContentType "application/json" ` 183 | -Method GET ` 184 | 185 | Write-Host "Retrieved Graph content" -ForegroundColor Green 186 | $Stoploop = $true 187 | } 188 | catch { 189 | if ($Retrycount -gt 5) { 190 | Write-Host "Could not get Graph content after 6 retries." -ForegroundColor Red 191 | $Stoploop = $true 192 | } 193 | else { 194 | Write-Host "Could not get Graph content. Retrying in 5 seconds..." -ForegroundColor DarkYellow 195 | Start-Sleep -Seconds 5 196 | $Retrycount ++ 197 | } 198 | } 199 | } 200 | While ($Stoploop -eq $false) 201 | 202 | $appInfo = [pscustomobject][ordered]@{ 203 | ApplicationName = $ApplicationName 204 | TenantName = $tenantName 205 | TenantId = $tenant_id 206 | clientId = $client_id 207 | clientSecret = $client_secret 208 | ApplicationPermissions = $ApplicationPermissions 209 | } 210 | 211 | $AppInfo | Export-Csv C:\temp\AzureADApp.csv -Append -NoTypeInformation 212 | } 213 | else { 214 | Write-Host "Microsoft Graph Service Principal could not be found or created" -ForegroundColor Red 215 | } -------------------------------------------------------------------------------- /ITGlue/SyncMicrosoftSecureScoreReports/CreateApp.ps1: -------------------------------------------------------------------------------- 1 | # This script needs to be run by an admin account in your Office 365 tenant 2 | # This script will create an Azure AD app in your organisation with permission 3 | # to access resources in yours and your customers' tenants. 4 | # It will export information about the application to a CSV located at C:\temp\. 5 | # The CSV will include the Client ID and Secret of the application, so keep it safe. 6 | 7 | # Confirm C:\temp exists 8 | $temp = Test-Path -Path C:\temp 9 | if ($temp) { 10 | #Write-Host "Path exists" 11 | } 12 | else { 13 | Write-Host "Creating Temp folder" 14 | New-Item -Path C:\temp -ItemType directory 15 | } 16 | 17 | $applicationName = "GCITS Secure Score Reader" 18 | 19 | # Change this to true if you would like to overwrite any existing applications with matching names. 20 | $removeExistingAppWithSameName = $false 21 | # Modify the homePage, appIdURI and logoutURI values to whatever valid URI you like. 22 | # They don't need to be actual addresses, so feel free to make something up. 23 | $homePage = "https://secure.gcits.com" 24 | $appIdURI = "https://secure.gcits.com/$((New-Guid).ToString())" 25 | $logoutURI = "https://portal.office.com" 26 | 27 | $URIForApplicationPermissionCall = "https://graph.microsoft.com/beta/security/secureScores" 28 | $ApplicationPermissions = "SecurityEvents.Read.All Directory.Read.All Sites.Manage.All" 29 | 30 | Function Add-ResourcePermission($requiredAccess, $exposedPermissions, $requiredAccesses, $permissionType) { 31 | foreach ($permission in $requiredAccesses.Trim().Split(" ")) { 32 | $reqPermission = $null 33 | $reqPermission = $exposedPermissions | Where-Object {$_.Value -contains $permission} 34 | Write-Host "Collected information for $($reqPermission.Value) of type $permissionType" -ForegroundColor Green 35 | $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess 36 | $resourceAccess.Type = $permissionType 37 | $resourceAccess.Id = $reqPermission.Id 38 | $requiredAccess.ResourceAccess.Add($resourceAccess) 39 | } 40 | } 41 | 42 | Function Get-RequiredPermissions($requiredDelegatedPermissions, $requiredApplicationPermissions, $reqsp) { 43 | $sp = $reqsp 44 | $appid = $sp.AppId 45 | $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess 46 | $requiredAccess.ResourceAppId = $appid 47 | $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess] 48 | if ($requiredDelegatedPermissions) { 49 | Add-ResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope" 50 | } 51 | if ($requiredApplicationPermissions) { 52 | Add-ResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role" 53 | } 54 | return $requiredAccess 55 | } 56 | Function New-AppKey ($fromDate, $durationInYears, $pw) { 57 | $endDate = $fromDate.AddYears($durationInYears) 58 | $keyId = (New-Guid).ToString() 59 | $key = New-Object Microsoft.Open.AzureAD.Model.PasswordCredential($null, $endDate, $keyId, $fromDate, $pw) 60 | return $key 61 | } 62 | 63 | Function Test-AppKey($fromDate, $durationInYears, $pw) { 64 | 65 | $testKey = New-AppKey -fromDate $fromDate -durationInYears $durationInYears -pw $pw 66 | while ($testKey.Value -match "\+" -or $testKey.Value -match "/") { 67 | Write-Host "Secret contains + or / and may not authenticate correctly. Regenerating..." -ForegroundColor Yellow 68 | $pw = Initialize-AppKey 69 | $testKey = New-AppKey -fromDate $fromDate -durationInYears $durationInYears -pw $pw 70 | } 71 | Write-Host "Secret doesn't contain + or /. Continuing..." -ForegroundColor Green 72 | $key = $testKey 73 | 74 | return $key 75 | } 76 | 77 | Function Initialize-AppKey { 78 | $aesManaged = New-Object "System.Security.Cryptography.AesManaged" 79 | $aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC 80 | $aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros 81 | $aesManaged.BlockSize = 128 82 | $aesManaged.KeySize = 256 83 | $aesManaged.GenerateKey() 84 | return [System.Convert]::ToBase64String($aesManaged.Key) 85 | } 86 | function Confirm-MicrosoftGraphServicePrincipal { 87 | $graphsp = Get-AzureADServicePrincipal -SearchString "Microsoft Graph" 88 | if (!$graphsp) { 89 | $graphsp = Get-AzureADServicePrincipal -SearchString "Microsoft.Azure.AgregatorService" 90 | } 91 | if (!$graphsp) { 92 | Login-AzureRmAccount -Credential $credentials 93 | New-AzureRmADServicePrincipal -ApplicationId "00000003-0000-0000-c000-000000000000" 94 | $graphsp = Get-AzureADServicePrincipal -SearchString "Microsoft Graph" 95 | } 96 | return $graphsp 97 | } 98 | Write-Host "Connecting to Azure AD. The login window may appear behind Visual Studio Code." 99 | Connect-AzureAD 100 | 101 | Write-Host "Creating application in tenant: $((Get-AzureADTenantDetail).displayName)" 102 | 103 | # Check for the Microsoft Graph Service Principal. If it doesn't exist already, create it. 104 | $graphsp = Confirm-MicrosoftGraphServicePrincipal 105 | 106 | $existingapp = $null 107 | $existingapp = get-azureadapplication -SearchString $applicationName 108 | if ($existingapp -and $removeExistingAppWithSameName) { 109 | Remove-Azureadapplication -ObjectId $existingApp.objectId 110 | } 111 | 112 | # RSPS 113 | $rsps = @() 114 | if ($graphsp) { 115 | $rsps += $graphsp 116 | $tenant_id = (Get-AzureADTenantDetail).ObjectId 117 | $tenantName = (Get-AzureADTenantDetail).DisplayName 118 | 119 | # Add Required Resources Access (Microsoft Graph) 120 | $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess] 121 | $microsoftGraphRequiredPermissions = Get-RequiredPermissions -reqsp $graphsp -requiredApplicationPermissions $ApplicationPermissions -requiredDelegatedPermissions $DelegatedPermissions 122 | $requiredResourcesAccess.Add($microsoftGraphRequiredPermissions) 123 | 124 | # Get an application key 125 | $pw = Initialize-AppKey 126 | $fromDate = [System.DateTime]::Now 127 | $appKey = Test-AppKey -fromDate $fromDate -durationInYears 99 -pw $pw 128 | 129 | Write-Host "Creating the AAD application $applicationName" -ForegroundColor Blue 130 | $aadApplication = New-AzureADApplication -DisplayName $applicationName ` 131 | -HomePage $homePage ` 132 | -ReplyUrls $homePage ` 133 | -IdentifierUris $appIdURI ` 134 | -LogoutUrl $logoutURI ` 135 | -RequiredResourceAccess $requiredResourcesAccess ` 136 | -PasswordCredentials $appKey ` 137 | -AvailableToOtherTenants $true 138 | 139 | # Creating the Service Principal for the application 140 | $servicePrincipal = New-AzureADServicePrincipal -AppId $aadApplication.AppId 141 | 142 | Write-Host "Assigning Permissions" -ForegroundColor Yellow 143 | 144 | # Assign application permissions to the application 145 | foreach ($app in $requiredResourcesAccess) { 146 | $reqAppSP = $rsps | Where-Object {$_.appid -contains $app.ResourceAppId} 147 | Write-Host "Assigning Application permissions for $($reqAppSP.displayName)" -ForegroundColor DarkYellow 148 | foreach ($resource in $app.ResourceAccess) { 149 | if ($resource.Type -match "Role") { 150 | New-AzureADServiceAppRoleAssignment -ObjectId $serviceprincipal.ObjectId ` 151 | -PrincipalId $serviceprincipal.ObjectId -ResourceId $reqAppSP.ObjectId -Id $resource.Id 152 | } 153 | } 154 | } 155 | 156 | # This provides the application with access to your customer tenants. 157 | $group = Get-AzureADGroup -Filter "displayName eq 'Adminagents'" 158 | Add-AzureADGroupMember -ObjectId $group.ObjectId -RefObjectId $servicePrincipal.ObjectId 159 | 160 | Write-Host "App Created" -ForegroundColor Green 161 | 162 | # Define parameters for Microsoft Graph access token retrieval 163 | $client_id = $aadApplication.AppId; 164 | $client_secret = $appkey.Value 165 | $tenant_id = (Get-AzureADTenantDetail).ObjectId 166 | $resource = "https://graph.microsoft.com" 167 | $authority = "https://login.microsoftonline.com/$tenant_id" 168 | $tokenEndpointUri = "$authority/oauth2/token" 169 | 170 | # Get the access token using grant type password for Delegated Permissions or grant type client_credentials for Application Permissions 171 | 172 | $content = "grant_type=client_credentials&client_id=$client_id&client_secret=$client_secret&resource=$resource" 173 | 174 | # Try to execute the API call 6 times 175 | 176 | $Stoploop = $false 177 | [int]$Retrycount = "0" 178 | do { 179 | try { 180 | $response = Invoke-RestMethod -Uri $tokenEndpointUri -Body $content -Method Post -UseBasicParsing 181 | Write-Host "Retrieved Access Token" -ForegroundColor Green 182 | # Assign access token 183 | $access_token = $response.access_token 184 | $body = $null 185 | 186 | $body = Invoke-RestMethod ` 187 | -Uri $UriForApplicationPermissionCall ` 188 | -Headers @{"Authorization" = "Bearer $access_token"} ` 189 | -ContentType "application/json" ` 190 | -Method GET ` 191 | 192 | Write-Host "Retrieved Graph content" -ForegroundColor Green 193 | $Stoploop = $true 194 | } 195 | catch { 196 | if ($Retrycount -gt 5) { 197 | Write-Host "Could not get Graph content after 6 retries." -ForegroundColor Red 198 | $Stoploop = $true 199 | } 200 | else { 201 | Write-Host "Could not get Graph content. Retrying in 5 seconds..." -ForegroundColor DarkYellow 202 | Start-Sleep -Seconds 5 203 | $Retrycount ++ 204 | } 205 | } 206 | } 207 | While ($Stoploop -eq $false) 208 | 209 | $appInfo = [pscustomobject][ordered]@{ 210 | ApplicationName = $ApplicationName 211 | TenantName = $tenantName 212 | TenantId = $tenant_id 213 | clientId = $client_id 214 | clientSecret = $client_secret 215 | ApplicationPermissions = $ApplicationPermissions 216 | } 217 | 218 | $AppInfo | Export-Csv C:\temp\AzureADApp.csv -Append -NoTypeInformation 219 | } 220 | else { 221 | Write-Host "Microsoft Graph Service Principal could not be found or created" -ForegroundColor Red 222 | } -------------------------------------------------------------------------------- /Office365/General/Export-AdministrativeUsers/README.md: -------------------------------------------------------------------------------- 1 | [Source](https://gcits.com/knowledge-base/get-list-every-customers-office-365-administrators-via-powershell-delegated-administration/ "Permalink to Get a list of every customers' Office 365 administrators via PowerShell and delegated administration") 2 | 3 | # Get a list of every customers' Office 365 administrators via PowerShell and delegated administration 4 | 5 | To increase security in our customer's Office 365 tenants, we're keeping track of all Global Administrators, and blocking access to any unnecessary users until we've reset the credentials and documented them securely. 6 | 7 | The type of user we're most concerned with is the unlicensed Company Administrator. This is usually a default user that's created when a customer or partner sets up Office 365. However, it may be the case that the person who set up or purchased Office 365 is not the person who needs to have Administrative credentials for their entire company. 8 | 9 | I've broken this process down into two scripts: 10 | 11 | 1. The first script checks every Office 365 customer for unlicensed users that are members of the Company Administrator role. Once it's finished, it exports the details to a CSV file. 12 | 2. The second script retrieves these unlicensed admins from the CSV and blocks their access. 13 | 14 | Important 15 | 16 | Before you run the second script, you should check the CSV to make sure that you're not blocking access to any essential users. In our case, we have a few service users that run third party Exchange backup products, as well as a few customers that use their admins for legitimate reasons. You'll need to manually remove these admins from the created CSV file before you run the second script. 17 | 18 | ### Looking for a different type of User Role? 19 | 20 | If you'd like a report on users with a different role, just modify the **$RoleName** variable in the first script. Here's a list of roles you can choose from: 21 | 22 | - Compliance Administrator 23 | - Exchange Service Administrator 24 | - Partner Tier 1 Support 25 | - Company Administrator 26 | - Helpdesk Administrator 27 | - Lync Service Administrator 28 | - Directory Readers 29 | - Directory Writers 30 | - Device Join 31 | - Device Administrators 32 | - Billing Administrator 33 | - Workplace Device Join 34 | - Directory Synchronization Accounts 35 | - Device Users 36 | - Partner Tier2 Support 37 | - Service Support Administrator 38 | - SharePoint Service Administrator 39 | - User Account Administrator 40 | 41 | ## Get a CSV of all Unlicensed Office 365 Admins via PowerShell using Delegated Administration 42 | 43 | ![Getting Customers Unlicensed Office 365 Admins via PowerShell][1] 44 | 45 | 1. You'll need to ensure you have the Azure Active Directory PowerShell Module installed, [follow our quick guide here for instructions][2]. 46 | 2. Copy and paste the following script into Visual Studio Code, PowerShell ISE, NotePad etc. 47 | 3. Save it with an extension of .ps1 and run it using Windows PowerShell 48 | 49 | ```powershell 50 | cls 51 | 52 | # This is the username of an Office 365 account with delegated admin permissions 53 | 54 | $UserName = "training@gcits.com" 55 | 56 | $Cred = get-credential -Credential $UserName 57 | 58 | #This script is looking for unlicensed Company Administrators. Though you can update the role here to look for another role type. 59 | 60 | $RoleName = "Company Administrator" 61 | 62 | Connect-MSOLService -Credential $Cred 63 | 64 | Import-Module MSOnline 65 | 66 | $Customers = Get-MsolPartnerContract -All 67 | 68 | $msolUserResults = @() 69 | 70 | # This is the path of the exported CSV. You'll need to create a C:temp folder. You can change this, though you'll need to update the next script with the new path. 71 | 72 | $msolUserCsv = "C:tempAdminUserList.csv" 73 | 74 | ForEach ($Customer in $Customers) { 75 | 76 | Write-Host "----------------------------------------------------------" 77 | Write-Host "Getting Unlicensed Admins for $($Customer.Name)" 78 | Write-Host " " 79 | 80 | 81 | $CompanyAdminRole = Get-MsolRole | Where-Object{$_.Name -match $RoleName} 82 | $RoleID = $CompanyAdminRole.ObjectID 83 | $Admins = Get-MsolRoleMember -TenantId $Customer.TenantId -RoleObjectId $RoleID 84 | 85 | foreach ($Admin in $Admins){ 86 | 87 | if($Admin.EmailAddress -ne $null){ 88 | 89 | $MsolUserDetails = Get-MsolUser -UserPrincipalName $Admin.EmailAddress -TenantId $Customer.TenantId 90 | 91 | if(!$Admin.IsLicensed){ 92 | 93 | $LicenseStatus = $MsolUserDetails.IsLicensed 94 | $userProperties = @{ 95 | 96 | TenantId = $Customer.TenantID 97 | CompanyName = $Customer.Name 98 | PrimaryDomain = $Customer.DefaultDomainName 99 | DisplayName = $Admin.DisplayName 100 | EmailAddress = $Admin.EmailAddress 101 | IsLicensed = $LicenseStatus 102 | BlockCredential = $MsolUserDetails.BlockCredential 103 | } 104 | 105 | Write-Host "$($Admin.DisplayName) from $($Customer.Name) is an unlicensed Company Admin" 106 | 107 | $msolUserResults += New-Object psobject -Property $userProperties 108 | } 109 | } 110 | } 111 | 112 | Write-Host " " 113 | 114 | } 115 | 116 | $msolUserResults | Select-Object TenantId,CompanyName,PrimaryDomain,DisplayName,EmailAddress,IsLicensed,BlockCredential | Export-Csv -notypeinformation -Path $msolUserCsv 117 | 118 | Write-Host "Export Complete" 119 | ``` 120 | 121 | ## Get a CSV of all Licensed AND Unlicensed Office 365 Admins via PowerShell using Delegated Administration 122 | 123 | The only difference between this script and the last one is that this one gets ALL administrators, licensed or not. 124 | 125 | 1. You'll need to ensure you have the Azure Active Directory PowerShell Module installed, [follow our quick guide here for instructions][2]. 126 | 2. Copy and paste the following script into Visual Studio Code, PowerShell ISE, NotePad etc. 127 | 3. Save it with an extension of .ps1 and run it using Windows PowerShell 128 | 129 | ```powershell 130 | cls 131 | 132 | # This is the username of an Office 365 account with delegated admin permissions 133 | 134 | $UserName = "training@gcits.com" 135 | 136 | $Cred = get-credential -Credential $UserName 137 | 138 | #This script is looking for unlicensed Company Administrators. Though you can update the role here to look for another role type. 139 | 140 | $RoleName = "Company Administrator" 141 | 142 | Connect-MSOLService -Credential $Cred 143 | 144 | Import-Module MSOnline 145 | 146 | $Customers = Get-MsolPartnerContract -All 147 | 148 | $msolUserResults = @() 149 | 150 | # This is the path of the exported CSV. You'll need to create a C:temp folder. You can change this, though you'll need to update the next script with the new path. 151 | 152 | $msolUserCsv = "C:tempAdminUserList.csv" 153 | 154 | ForEach ($Customer in $Customers) { 155 | 156 | Write-Host "----------------------------------------------------------" 157 | Write-Host "Getting Unlicensed Admins for $($Customer.Name)" 158 | Write-Host " " 159 | 160 | 161 | $CompanyAdminRole = Get-MsolRole | Where-Object{$_.Name -match $RoleName} 162 | $RoleID = $CompanyAdminRole.ObjectID 163 | $Admins = Get-MsolRoleMember -TenantId $Customer.TenantId -RoleObjectId $RoleID 164 | 165 | foreach ($Admin in $Admins){ 166 | 167 | if($Admin.EmailAddress -ne $null){ 168 | 169 | $MsolUserDetails = Get-MsolUser -UserPrincipalName $Admin.EmailAddress -TenantId $Customer.TenantId 170 | 171 | $LicenseStatus = $MsolUserDetails.IsLicensed 172 | $userProperties = @{ 173 | 174 | TenantId = $Customer.TenantID 175 | CompanyName = $Customer.Name 176 | PrimaryDomain = $Customer.DefaultDomainName 177 | DisplayName = $Admin.DisplayName 178 | EmailAddress = $Admin.EmailAddress 179 | IsLicensed = $LicenseStatus 180 | BlockCredential = $MsolUserDetails.BlockCredential 181 | } 182 | 183 | Write-Host "$($Admin.DisplayName) from $($Customer.Name) is an unlicensed Company Admin" 184 | 185 | $msolUserResults += New-Object psobject -Property $userProperties 186 | 187 | } 188 | } 189 | 190 | Write-Host " " 191 | 192 | } 193 | 194 | $msolUserResults | Select-Object TenantId,CompanyName,PrimaryDomain,DisplayName,EmailAddress,IsLicensed,BlockCredential | Export-Csv -notypeinformation -Path $msolUserCsv 195 | 196 | Write-Host "Export Complete" 197 | ``` 198 | 199 | ## Block access to the Office 365 Admins in the CSV file 200 | 201 | 1. Make sure you've thoroughly checked the exported CSV file and removed any essential Office 365 admins. 202 | 2. Copy and paste the following script into Visual Studio Code, PowerShell ISE, NotePad etc. 203 | 3. Save it with an extension of .ps1 and run it using Windows PowerShell 204 | 205 | ```powershell 206 | cls 207 | 208 | # This is the username of an Office 365 account with delegated admin permissions 209 | 210 | $UserName = "training@gcits.com" 211 | 212 | $Cred = get-credential -Credential $UserName 213 | 214 | $users = import-csv "C:tempAdminUserList.csv" 215 | 216 | Connect-MsolService -Credential $cred 217 | 218 | ForEach ($user in $users) { 219 | 220 | $tenantID = $user.tenantid 221 | 222 | $upn = $user.EmailAddress 223 | 224 | Write-Output "Blocking sign in for: $upn" 225 | 226 | Set-MsolUser -TenantId $tenantID -UserPrincipalName $upn -BlockCredential $true 227 | 228 | } 229 | ``` 230 | 231 | ## How to re-enable Office 365 admins via PowerShell using delegated administration 232 | 233 | In our case, we'll be re-enabling these users and resetting their credentials when it comes time to connect to them via Exchange Online. [See our guide for an example of how to reenable these users when required][3]. 234 | 235 | ### About The Author 236 | 237 | ![Elliot Munro][4] 238 | 239 | #### [ Elliot Munro ][5] 240 | 241 | Elliot Munro is an Office 365 MCSA from the Gold Coast, Australia supporting hundreds of small businesses with GCITS. If you have an Office 365 or Azure issue that you'd like us to take a look at (or have a request for a useful script) send Elliot an email at [elliot@gcits.com][6] 242 | 243 | [1]: https://gcits.com.au/wp-content/uploads/GettingUnlicensedAdmins.png 244 | [2]: https://gcits.com.au/knowledge-base/install-azure-active-directory-powershell-module/ 245 | [3]: https://gcits.com.au/knowledge-base/managing-users-in-office-365-delegated-tenants-via-powershell/ 246 | [4]: https://gcits.com/wp-content/uploads/AAEAAQAAAAAAAA2QAAAAJDNlN2NmM2Y4LTU5YWYtNGRiNC1hMmI2LTBhMzdhZDVmNWUzNA-80x80.jpg 247 | [5]: https://gcits.com/author/elliotmunro/ 248 | [6]: mailto:elliot%40gcits.com 249 | -------------------------------------------------------------------------------- /ITGlue/Office365UserActivityAndUsage/CreateApp.ps1: -------------------------------------------------------------------------------- 1 | # This script needs to be run by an admin account in your Office 365 tenant 2 | # This script will create an Azure AD app in your organisation with permission 3 | # to access resources in yours and your customers' tenants. 4 | # It will export information about the application to a CSV located at C:\temp\. 5 | # The CSV will include the Client ID and Secret of the application, so keep it safe. 6 | 7 | # Confirm C:\temp exists 8 | $temp = Test-Path -Path C:\temp 9 | if ($temp) { 10 | #Write-Host "Path exists" 11 | } 12 | else { 13 | Write-Host "Creating Temp folder" 14 | New-Item -Path C:\temp -ItemType directory 15 | } 16 | 17 | $applicationName = "GCITS User Activity Report Reader" 18 | 19 | # Change this to true if you would like to overwrite any existing applications with matching names. 20 | $removeExistingAppWithSameName = $false 21 | # Modify the homePage, appIdURI and logoutURI values to whatever valid URI you like. 22 | # They don't need to be actual addresses, so feel free to make something up (as long as it's on a verified domain in your Office 365 environment eg. https://anything.yourdomain.com). 23 | $homePage = "https://secure.gcits.com" 24 | $appIdURI = "https://secure.gcits.com/$((New-Guid).ToString())" 25 | $logoutURI = "https://portal.office.com" 26 | 27 | $URIForApplicationPermissionCall = "https://graph.microsoft.com/beta/reports/getMailboxUsageDetail(period='D7')?`$format=application/json" 28 | $ApplicationPermissions = "Reports.Read.All Directory.Read.All Sites.Manage.All" 29 | 30 | Function Add-ResourcePermission($requiredAccess, $exposedPermissions, $requiredAccesses, $permissionType) { 31 | foreach ($permission in $requiredAccesses.Trim().Split(" ")) { 32 | $reqPermission = $null 33 | $reqPermission = $exposedPermissions | Where-Object {$_.Value -contains $permission} 34 | Write-Host "Collected information for $($reqPermission.Value) of type $permissionType" -ForegroundColor Green 35 | $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess 36 | $resourceAccess.Type = $permissionType 37 | $resourceAccess.Id = $reqPermission.Id 38 | $requiredAccess.ResourceAccess.Add($resourceAccess) 39 | } 40 | } 41 | 42 | Function Get-RequiredPermissions($requiredDelegatedPermissions, $requiredApplicationPermissions, $reqsp) { 43 | $sp = $reqsp 44 | $appid = $sp.AppId 45 | $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess 46 | $requiredAccess.ResourceAppId = $appid 47 | $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess] 48 | if ($requiredDelegatedPermissions) { 49 | Add-ResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope" 50 | } 51 | if ($requiredApplicationPermissions) { 52 | Add-ResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role" 53 | } 54 | return $requiredAccess 55 | } 56 | Function New-AppKey ($fromDate, $durationInYears, $pw) { 57 | $endDate = $fromDate.AddYears($durationInYears) 58 | $keyId = (New-Guid).ToString() 59 | $key = New-Object Microsoft.Open.AzureAD.Model.PasswordCredential($null, $endDate, $keyId, $fromDate, $pw) 60 | return $key 61 | } 62 | 63 | Function Test-AppKey($fromDate, $durationInYears, $pw) { 64 | 65 | $testKey = New-AppKey -fromDate $fromDate -durationInYears $durationInYears -pw $pw 66 | while ($testKey.Value -match "\+" -or $testKey.Value -match "/") { 67 | Write-Host "Secret contains + or / and may not authenticate correctly. Regenerating..." -ForegroundColor Yellow 68 | $pw = Initialize-AppKey 69 | $testKey = New-AppKey -fromDate $fromDate -durationInYears $durationInYears -pw $pw 70 | } 71 | Write-Host "Secret doesn't contain + or /. Continuing..." -ForegroundColor Green 72 | $key = $testKey 73 | 74 | return $key 75 | } 76 | 77 | Function Initialize-AppKey { 78 | $aesManaged = New-Object "System.Security.Cryptography.AesManaged" 79 | $aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC 80 | $aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros 81 | $aesManaged.BlockSize = 128 82 | $aesManaged.KeySize = 256 83 | $aesManaged.GenerateKey() 84 | return [System.Convert]::ToBase64String($aesManaged.Key) 85 | } 86 | function Confirm-MicrosoftGraphServicePrincipal { 87 | $graphsp = Get-AzureADServicePrincipal -SearchString "Microsoft Graph" 88 | if (!$graphsp) { 89 | $graphsp = Get-AzureADServicePrincipal -SearchString "Microsoft.Azure.AgregatorService" 90 | } 91 | if (!$graphsp) { 92 | Login-AzureRmAccount -Credential $credentials 93 | New-AzureRmADServicePrincipal -ApplicationId "00000003-0000-0000-c000-000000000000" 94 | $graphsp = Get-AzureADServicePrincipal -SearchString "Microsoft Graph" 95 | } 96 | return $graphsp 97 | } 98 | Write-Host "Connecting to Azure AD. The login window may appear behind Visual Studio Code." 99 | Connect-AzureAD 100 | 101 | Write-Host "Creating application in tenant: $((Get-AzureADTenantDetail).displayName)" 102 | 103 | # Check for the Microsoft Graph Service Principal. If it doesn't exist already, create it. 104 | $graphsp = Confirm-MicrosoftGraphServicePrincipal 105 | 106 | $existingapp = $null 107 | $existingapp = get-azureadapplication -SearchString $applicationName 108 | if ($existingapp -and $removeExistingAppWithSameName) { 109 | Remove-Azureadapplication -ObjectId $existingApp.objectId 110 | } 111 | 112 | # RSPS 113 | $rsps = @() 114 | if ($graphsp) { 115 | $rsps += $graphsp 116 | $tenant_id = (Get-AzureADTenantDetail).ObjectId 117 | $tenantName = (Get-AzureADTenantDetail).DisplayName 118 | 119 | # Add Required Resources Access (Microsoft Graph) 120 | $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess] 121 | $microsoftGraphRequiredPermissions = Get-RequiredPermissions -reqsp $graphsp -requiredApplicationPermissions $ApplicationPermissions -requiredDelegatedPermissions $DelegatedPermissions 122 | $requiredResourcesAccess.Add($microsoftGraphRequiredPermissions) 123 | 124 | # Get an application key 125 | $pw = Initialize-AppKey 126 | $fromDate = [System.DateTime]::Now 127 | $appKey = Test-AppKey -fromDate $fromDate -durationInYears 99 -pw $pw 128 | 129 | Write-Host "Creating the AAD application $applicationName" -ForegroundColor Blue 130 | $aadApplication = New-AzureADApplication -DisplayName $applicationName ` 131 | -HomePage $homePage ` 132 | -ReplyUrls $homePage ` 133 | -IdentifierUris $appIdURI ` 134 | -LogoutUrl $logoutURI ` 135 | -RequiredResourceAccess $requiredResourcesAccess ` 136 | -PasswordCredentials $appKey ` 137 | -AvailableToOtherTenants $true 138 | 139 | # Creating the Service Principal for the application 140 | $servicePrincipal = New-AzureADServicePrincipal -AppId $aadApplication.AppId 141 | 142 | Write-Host "Assigning Permissions" -ForegroundColor Yellow 143 | 144 | # Assign application permissions to the application 145 | foreach ($app in $requiredResourcesAccess) { 146 | $reqAppSP = $rsps | Where-Object {$_.appid -contains $app.ResourceAppId} 147 | Write-Host "Assigning Application permissions for $($reqAppSP.displayName)" -ForegroundColor DarkYellow 148 | foreach ($resource in $app.ResourceAccess) { 149 | if ($resource.Type -match "Role") { 150 | New-AzureADServiceAppRoleAssignment -ObjectId $serviceprincipal.ObjectId ` 151 | -PrincipalId $serviceprincipal.ObjectId -ResourceId $reqAppSP.ObjectId -Id $resource.Id 152 | } 153 | } 154 | } 155 | 156 | # This provides the application with access to your customer tenants. 157 | $group = Get-AzureADGroup -Filter "displayName eq 'Adminagents'" 158 | Add-AzureADGroupMember -ObjectId $group.ObjectId -RefObjectId $servicePrincipal.ObjectId 159 | 160 | Write-Host "App Created" -ForegroundColor Green 161 | 162 | # Define parameters for Microsoft Graph access token retrieval 163 | $client_id = $aadApplication.AppId; 164 | $client_secret = $appkey.Value 165 | $tenant_id = (Get-AzureADTenantDetail).ObjectId 166 | $resource = "https://graph.microsoft.com" 167 | $authority = "https://login.microsoftonline.com/$tenant_id" 168 | $tokenEndpointUri = "$authority/oauth2/token" 169 | 170 | # Get the access token using grant type password for Delegated Permissions or grant type client_credentials for Application Permissions 171 | 172 | $content = "grant_type=client_credentials&client_id=$client_id&client_secret=$client_secret&resource=$resource" 173 | 174 | # Try to execute the API call 6 times 175 | 176 | $Stoploop = $false 177 | [int]$Retrycount = "0" 178 | do { 179 | try { 180 | $response = Invoke-RestMethod -Uri $tokenEndpointUri -Body $content -Method Post -UseBasicParsing 181 | Write-Host "Retrieved Access Token" -ForegroundColor Green 182 | # Assign access token 183 | $access_token = $response.access_token 184 | $body = $null 185 | 186 | $body = Invoke-RestMethod ` 187 | -Uri $UriForApplicationPermissionCall ` 188 | -Headers @{"Authorization" = "Bearer $access_token"} ` 189 | -ContentType "application/json" ` 190 | -Method GET ` 191 | 192 | Write-Host "Retrieved Graph content" -ForegroundColor Green 193 | $Stoploop = $true 194 | } 195 | catch { 196 | if ($Retrycount -gt 5) { 197 | Write-Host "Could not get Graph content after 6 retries." -ForegroundColor Red 198 | $Stoploop = $true 199 | } 200 | else { 201 | Write-Host "Could not get Graph content. Retrying in 5 seconds..." -ForegroundColor DarkYellow 202 | Start-Sleep -Seconds 5 203 | $Retrycount ++ 204 | } 205 | } 206 | } 207 | While ($Stoploop -eq $false) 208 | 209 | $appInfo = [pscustomobject][ordered]@{ 210 | ApplicationName = $ApplicationName 211 | TenantName = $tenantName 212 | TenantId = $tenant_id 213 | clientId = $client_id 214 | clientSecret = $client_secret 215 | ApplicationPermissions = $ApplicationPermissions 216 | } 217 | 218 | $AppInfo | Export-Csv C:\temp\AzureADApp.csv -Append -NoTypeInformation 219 | } 220 | else { 221 | Write-Host "Microsoft Graph Service Principal could not be found or created" -ForegroundColor Red 222 | } -------------------------------------------------------------------------------- /Office365/OneDrive/Transfer-OneDrivetoOneDrive/README.md: -------------------------------------------------------------------------------- 1 | [Source](https://gcits.com/knowledge-base/transfer-users-onedrive-files-another-user-via-powershell/ "Permalink to Transfer all OneDrive files to another user via PowerShell") 2 | 3 | # Transfer all OneDrive files to another user via PowerShell 4 | 5 | ![OneDrive Transfer Via PowerShell][1] 6 | 7 | During the offboarding of an Office 365 user, you may be required to make a copy of their OneDrive files or transfer ownership of the files to someone else. This can be a laborious process, requiring you to log into the departing users OneDrive and downloading, transferring or sharing their data manually. 8 | 9 | The good news is, we can do this with PowerShell. The below script makes it relatively easy to copy a user's entire OneDrive directory into a subfolder in another user's OneDrive. 10 | 11 | The bad news is, there are a few things you'll need to consider before you use it: 12 | 13 | ## Things to keep in mind 14 | 15 | - **This could take a while.** 16 | Each file or folder takes at least a second or two to process. If your departing user has tens of thousands of files, and time is not on your side, you may want to use another method. 17 | - **It uses your connection** 18 | It downloads the files before uploading them to the other user's OneDrive. If your connection is slow, and you're moving large files you might want to leave this running on a cloud hosted server. 19 | - **It can't move files larger than 250MB** 20 | A limitation of this PowerShell module is that it can't send files larger than 250MB to SharePoint. This script will make a note of these files and export a list of them to c:templargefiles.txt in case you want to move them manually. 21 | - **No Two Factor Authentication** 22 | This script doesn't work with multifactor authentication on the admin account. You may want to create a temporary admin without MFA for this purpose. 23 | 24 | ## Prerequisites 25 | 26 | For this script to work, you'll need to install the following PowerShell Modules: 27 | 28 | ### SharePoint Online Management Shell 29 | 30 | The **SharePoint Online Management Shell** is used to modify the permissions on the users' OneDrive site collections. 31 | 32 | Download and install it here: [https://www.microsoft.com/en-au/download/details.aspx?id=35588][2] 33 | 34 | ### SharePoint PnP Powershell Module 35 | 36 | The **SharePoint PnP Powershell** module provides the cmdlets we'll use to transfer the files and folders between OneDrive accounts. 37 | 38 | To install it, open a PowerShell window as an administrator and run the following cmdlet: 39 | 40 | ```powershell 41 | Install-Module SharePointPnPPowerShellOnline -Force 42 | ``` 43 | 44 | ### MSOnline V1 Powershell Module 45 | 46 | You'll also need the MSOnline V1 PowerShell Module for this script. 47 | 48 | To install it, open a PowerShell window as an administrator and run the following cmdlet: 49 | 50 | ```powershell 51 | Install-Module MSOnline -Force 52 | ``` 53 | 54 | ## How to copy OneDrive files between users via PowerShell 55 | 56 | 1. Open Visual Studio Code, or PowerShell ISE and copy and paste the script at the bottom of this article. 57 | 2. Run it by pressing F5 58 | 3. Follow the prompts, entering the following info: 59 | **The username of the departing user.** This is the user whose OneDrive we'll be copying 60 | **The username of the destination user.** This is the user that will receive the OneDrive files in a subfolder within their OneDrive 61 | **The username of your Office 365 Admin.**![Enter Details For OneDrive Transfer][3] 62 | 4. The script will check for files too large to be transferred. If there are any, their details will be logged in C:templargefiles.txt![OneDrive Large Files][4] 63 | 5. Wait for the folders and files to be created. Folders are created first, so that the Copy-PnPFile cmdlet has an existing path to place the files.![Wait For OneDrive Files To Copy Between Users][5] 64 | 6. Once it's done, you'll find the files and folders in the destination users OneDrive under a subfolder called "Departing User" files. Where Departing User is the display name of the user that's leaving.![Files Located In Destination OneDrive][6] 65 | 66 | ## Complete PowerShell script to transfer OneDrive data to another user 67 | 68 | ```powershell 69 | $departinguser = Read-Host "Enter departing user's email" 70 | $destinationuser = Read-Host "Enter destination user's email" 71 | $globaladmin = Read-Host "Enter the username of your Global Admin account" 72 | $credentials = Get-Credential -Credential $globaladmin 73 | Connect-MsolService -Credential $credentials 74 | 75 | $InitialDomain = Get-MsolDomain | Where-Object {$_.IsInitial -eq $true} 76 | 77 | $SharePointAdminURL = "https://$($InitialDomain.Name.Split(".")[0])-admin.sharepoint.com" 78 | 79 | $departingUserUnderscore = $departinguser -replace "[^a-zA-Z]", "_" 80 | $destinationUserUnderscore = $destinationuser -replace "[^a-zA-Z]", "_" 81 | 82 | $departingOneDriveSite = "https://$($InitialDomain.Name.Split(".")[0])-my.sharepoint.com/personal/$departingUserUnderscore" 83 | $destinationOneDriveSite = "https://$($InitialDomain.Name.Split(".")[0])-my.sharepoint.com/personal/$destinationUserUnderscore" 84 | Write-Host "`nConnecting to SharePoint Online" -ForegroundColor Blue 85 | Connect-SPOService -Url $SharePointAdminURL -Credential $credentials 86 | 87 | Write-Host "`nAdding $globaladmin as site collection admin on both OneDrive site collections" -ForegroundColor Blue 88 | # Set current admin as a Site Collection Admin on both OneDrive Site Collections 89 | Set-SPOUser -Site $departingOneDriveSite -LoginName $globaladmin -IsSiteCollectionAdmin $true 90 | Set-SPOUser -Site $destinationOneDriveSite -LoginName $globaladmin -IsSiteCollectionAdmin $true 91 | 92 | Write-Host "`nConnecting to $departinguser's OneDrive via SharePoint Online PNP module" -ForegroundColor Blue 93 | 94 | Connect-PnPOnline -Url $departingOneDriveSite -Credentials $credentials 95 | 96 | Write-Host "`nGetting display name of $departinguser" -ForegroundColor Blue 97 | # Get name of departing user to create folder name. 98 | $departingOwner = Get-PnPSiteCollectionAdmin | Where-Object {$_.loginname -match $departinguser} 99 | 100 | # If there's an issue retrieving the departing user's display name, set this one. 101 | if ($departingOwner -contains $null) { 102 | $departingOwner = @{ 103 | Title = "Departing User" 104 | } 105 | } 106 | 107 | # Define relative folder locations for OneDrive source and destination 108 | $departingOneDrivePath = "/personal/$departingUserUnderscore/Documents" 109 | $destinationOneDrivePath = "/personal/$destinationUserUnderscore/Documents/$($departingOwner.Title)'s Files" 110 | $destinationOneDriveSiteRelativePath = "Documents/$($departingOwner.Title)'s Files" 111 | 112 | Write-Host "`nGetting all items from $($departingOwner.Title)" -ForegroundColor Blue 113 | # Get all items from source OneDrive 114 | $items = Get-PnPListItem -List Documents -PageSize 1000 115 | 116 | $largeItems = $items | Where-Object {[long]$_.fieldvalues.SMTotalFileStreamSize -ge 261095424 -and $_.FileSystemObjectType -contains "File"} 117 | if ($largeItems) { 118 | $largeexport = @() 119 | foreach ($item in $largeitems) { 120 | $largeexport += "$(Get-Date) - Size: $([math]::Round(($item.FieldValues.SMTotalFileStreamSize / 1MB),2)) MB Path: $($item.FieldValues.FileRef)" 121 | Write-Host "File too large to copy: $($item.FieldValues.FileRef)" -ForegroundColor DarkYellow 122 | } 123 | $largeexport | Out-file C:templargefiles.txt -Append 124 | Write-Host "A list of files too large to be copied from $($departingOwner.Title) have been exported to C:tempLargeFiles.txt" -ForegroundColor Yellow 125 | } 126 | 127 | $rightSizeItems = $items | Where-Object {[long]$_.fieldvalues.SMTotalFileStreamSize -lt 261095424 -or $_.FileSystemObjectType -contains "Folder"} 128 | 129 | Write-Host "`nConnecting to $destinationuser via SharePoint PNP PowerShell module" -ForegroundColor Blue 130 | Connect-PnPOnline -Url $destinationOneDriveSite -Credentials $credentials 131 | 132 | Write-Host "`nFilter by folders" -ForegroundColor Blue 133 | # Filter by Folders to create directory structure 134 | $folders = $rightSizeItems | Where-Object {$_.FileSystemObjectType -contains "Folder"} 135 | 136 | Write-Host "`nCreating Directory Structure" -ForegroundColor Blue 137 | foreach ($folder in $folders) { 138 | $path = ('{0}{1}' -f $destinationOneDriveSiteRelativePath, $folder.fieldvalues.FileRef).Replace($departingOneDrivePath, '') 139 | Write-Host "Creating folder in $path" -ForegroundColor Green 140 | $newfolder = Ensure-PnPFolder -SiteRelativePath $path 141 | } 142 | 143 | 144 | Write-Host "`nCopying Files" -ForegroundColor Blue 145 | $files = $rightSizeItems | Where-Object {$_.FileSystemObjectType -contains "File"} 146 | $fileerrors = "" 147 | foreach ($file in $files) { 148 | 149 | $destpath = ("$destinationOneDrivePath$($file.fieldvalues.FileDirRef)").Replace($departingOneDrivePath, "") 150 | Write-Host "Copying $($file.fieldvalues.FileLeafRef) to $destpath" -ForegroundColor Green 151 | $newfile = Copy-PnPFile -SourceUrl $file.fieldvalues.FileRef -TargetUrl $destpath -OverwriteIfAlreadyExists -Force -ErrorVariable errors -ErrorAction SilentlyContinue 152 | $fileerrors += $errors 153 | } 154 | $fileerrors | Out-File c:tempfileerrors.txt 155 | 156 | # Remove Global Admin from Site Collection Admin role for both users 157 | Write-Host "`nRemoving $globaladmin from OneDrive site collections" -ForegroundColor Blue 158 | Set-SPOUser -Site $departingOneDriveSite -LoginName $globaladmin -IsSiteCollectionAdmin $false 159 | Set-SPOUser -Site $destinationOneDriveSite -LoginName $globaladmin -IsSiteCollectionAdmin $false 160 | Write-Host "`nComplete!" -ForegroundColor Green 161 | ``` 162 | 163 | ### About The Author 164 | 165 | ![Elliot Munro][7] 166 | 167 | #### [ Elliot Munro ][8] 168 | 169 | Elliot Munro is an Office 365 MCSA from the Gold Coast, Australia supporting hundreds of small businesses with GCITS. If you have an Office 365 or Azure issue that you'd like us to take a look at (or have a request for a useful script) send Elliot an email at [elliot@gcits.com][9] 170 | 171 | [1]: https://gcits.com/wp-content/uploads/OneDriveTransferViaPowerShell-1030x436.png 172 | [2]: //www.microsoft.com/en-au/download/details.aspx?id=35588 173 | [3]: https://gcits.com/wp-content/uploads/EnterDetailsForOneDriveTransfer-1030x245.png 174 | [4]: https://gcits.com/wp-content/uploads/OneDriveLargeFiles.png 175 | [5]: https://gcits.com/wp-content/uploads/WaitForOneDriveFilesToCopyBetweenUsers-1030x238.png 176 | [6]: https://gcits.com/wp-content/uploads/FilesLocatedInDestinationOneDrive.png 177 | [7]: https://gcits.com/wp-content/uploads/AAEAAQAAAAAAAA2QAAAAJDNlN2NmM2Y4LTU5YWYtNGRiNC1hMmI2LTBhMzdhZDVmNWUzNA-80x80.jpg 178 | [8]: https://gcits.com/author/elliotmunro/ 179 | [9]: mailto:elliot%40gcits.com 180 | -------------------------------------------------------------------------------- /ITGlue/UniFiSync/UniFiSharePointMapping.ps1: -------------------------------------------------------------------------------- 1 | <# This script will connect to IT Glue, your Unifi Controller and SharePoint and attempt to match your Unifi Sites with the appropriate IT Glue Organisation. It will then add the site and its possible IT Glue organisation match to a SharePoint List called 'Unifi - IT Glue Site Match Register' within your root SharePoint site (eg. yourtenantname.sharepoint.com). It will first attempt to match the name of the site with the name of the customer. If it can't find a match, it will try to match the MAC addresses from the configurations in IT Glue against any of the MAC addresses of the most recent 200 devices that have connected to your Unifi site. If it still cannot find a match, it will add the site to the SharePoint list where you can manually match it. #> 2 | 3 | # IT Glue Details 4 | $ITGbaseURI = "https://api.itglue.com" 5 | $key = "EnterITGlueAPIKeyHere" 6 | 7 | 8 | # SharePoint Details 9 | $client_id = "EnterSharePointClientIDHere" 10 | $client_secret = "EnterSharePointClientSecretHere" 11 | $tenant_id = "EnterAzureADTenantIDHere" 12 | $ITGOrgRegisterListName = "ITGlue Org Register" 13 | $ListName = "Unifi - IT Glue match register" 14 | $graphBaseUri = "https://graph.microsoft.com/v1.0/" 15 | $siteid = "root" 16 | 17 | # UniFi Details 18 | $UnifiBaseUri = "https://unifi.yourdomain.com:8443" 19 | $UniFiCredentials = @{ 20 | username = "EnterUnifiAdminUserNameHere" 21 | password = "EnterUnifiAdminPasswordHere" 22 | remember = $true 23 | } | ConvertTo-Json 24 | 25 | $UnifiBaseUri = "$UnifiBaseUri/api" 26 | 27 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 28 | 29 | function New-GCITSSharePointColumn($Name, $Type, $Indexed, $lookupListName, $lookupColumnPrimaryName, $lookupColumnName) { 30 | 31 | $column = [ordered]@{ 32 | name = $Name 33 | indexed = $Indexed 34 | $Type = @{} 35 | } 36 | 37 | if ($lookupListName -and $type -contains "lookup") { 38 | $list = Get-GCITSSharePointList -ListName $lookupListName 39 | if ($list) { 40 | $column.lookup.listId = $list.id 41 | $column.lookup.columnName = $lookupColumnName 42 | } 43 | } 44 | return $column 45 | } 46 | 47 | function New-GCITSSharePointList ($Name, $ColumnCollection) { 48 | $list = @{ 49 | displayName = $Name 50 | columns = $columnCollection 51 | } | Convertto-json -Depth 10 52 | 53 | $newList = Invoke-RestMethod ` 54 | -Uri "$graphBaseUri/sites/$siteid/lists/" ` 55 | -Headers $SPHeaders ` 56 | -ContentType "application/json" ` 57 | -Method POST -Body $list 58 | return $newList 59 | } 60 | 61 | function Remove-GCITSSharePointList ($ListId) { 62 | $removeList = Invoke-RestMethod ` 63 | -Uri "$graphBaseUri/sites/$siteid/lists/$ListId" ` 64 | -Headers $SPHeaders ` 65 | -ContentType "application/json" ` 66 | -Method DELETE 67 | return $removeList 68 | } 69 | 70 | function Remove-GCITSSharePointListItem ($ListId, $ItemId) { 71 | $removeItem = Invoke-RestMethod ` 72 | -Uri "$graphBaseUri/sites/$siteid/lists/$ListId/items/$ItemId" ` 73 | -Headers $SPHeaders ` 74 | -ContentType "application/json" ` 75 | -Method DELETE 76 | return $removeItem 77 | } 78 | 79 | function New-GCITSSharePointListItem($ItemObject, $ListId) { 80 | 81 | $itemBody = @{ 82 | fields = $ItemObject 83 | } | ConvertTo-Json -Depth 10 84 | 85 | $listItem = Invoke-RestMethod ` 86 | -Uri "$graphBaseUri/sites/$siteid/lists/$listId/items" ` 87 | -Headers $SPHeaders ` 88 | -ContentType "application/json" ` 89 | -Method Post ` 90 | -Body $itemBody 91 | } 92 | 93 | function Get-GCITSSharePointListItem($ListId, $ItemId, $Query) { 94 | 95 | if ($ItemId) { 96 | $listItem = Invoke-RestMethod -Uri $graphBaseUri/sites/$siteid/lists/$listId/items/$ItemId ` 97 | -Method Get -headers $SPHeaders ` 98 | -ContentType application/json 99 | $value = $listItem 100 | } 101 | elseif ($Query) { 102 | $listItems = $null 103 | $listItems = Invoke-RestMethod -Uri "$graphBaseUri/sites/$siteid/lists/$listId/items/?expand=fields&`$filter=$Query" ` 104 | -Method Get -headers $SPHeaders ` 105 | -ContentType application/json 106 | $value = @() 107 | $value = $listItems.value 108 | if ($listitems."@odata.nextLink") { 109 | $nextLink = $true 110 | } 111 | if ($nextLink) { 112 | do { 113 | $listItems = Invoke-RestMethod -Uri $listitems."@odata.nextLink"` 114 | -Method Get -headers $SPHeaders ` 115 | -ContentType application/json 116 | $value += $listItems.value 117 | if (!$listitems."@odata.nextLink") { 118 | $nextLink = $false 119 | } 120 | } until (!$nextLink) 121 | } 122 | } 123 | else { 124 | $listItems = $null 125 | $listItems = Invoke-RestMethod -Uri $graphBaseUri/sites/$siteid/lists/$listId/items?expand=fields ` 126 | -Method Get -headers $SPHeaders ` 127 | -ContentType application/json 128 | $value = @() 129 | $value = $listItems.value 130 | if ($listitems."@odata.nextLink") { 131 | $nextLink = $true 132 | } 133 | if ($nextLink) { 134 | do { 135 | $listItems = Invoke-RestMethod -Uri $listitems."@odata.nextLink"` 136 | -Method Get -headers $SPHeaders ` 137 | -ContentType application/json 138 | $value += $listItems.value 139 | if (!$listitems."@odata.nextLink") { 140 | $nextLink = $false 141 | } 142 | } until (!$nextLink) 143 | } 144 | } 145 | return $value 146 | } 147 | 148 | function Set-GCITSSharePointListItem($ListId, $ItemId, $ItemObject) { 149 | $listItem = Invoke-RestMethod -Uri $graphBaseUri/sites/$siteid/lists/$listId/items/$ItemId/fields ` 150 | -Method Patch -headers $SPHeaders ` 151 | -ContentType application/json ` 152 | -Body ($itemObject | ConvertTo-Json) 153 | $return = $listItem 154 | } 155 | 156 | function Get-GCITSAccessToken { 157 | $authority = "https://login.microsoftonline.com/$tenant_id" 158 | $tokenEndpointUri = "$authority/oauth2/token" 159 | $resource = "https://graph.microsoft.com" 160 | $content = "grant_type=client_credentials&client_id=$client_id&client_secret=$client_secret&resource=$resource" 161 | $graphBaseUri = "https://graph.microsoft.com/v1.0" 162 | $response = Invoke-RestMethod -Uri $tokenEndpointUri -Body $content -Method Post -UseBasicParsing 163 | $access_token = $response.access_token 164 | return $access_token 165 | } 166 | function Get-GCITSSharePointList($ListName) { 167 | $list = Invoke-RestMethod ` 168 | -Uri "$graphBaseUri/sites/$siteid/lists?expand=columns&`$filter=displayName eq '$ListName'" ` 169 | -Headers $SPHeaders ` 170 | -ContentType "application/json" ` 171 | -Method GET 172 | $list = $list.value 173 | return $list 174 | } 175 | 176 | function Get-ITGlueItem($Resource) { 177 | $array = @() 178 | 179 | $body = Invoke-RestMethod -Method get -Uri "$ITGbaseUri/$Resource" -Headers $headers -ContentType application/vnd.api+json 180 | $array += $body.data 181 | Write-Host "Retrieved $($array.Count) IT Glue items" 182 | 183 | if ($body.links.next) { 184 | do { 185 | $body = Invoke-RestMethod -Method get -Uri $body.links.next -Headers $headers -ContentType application/vnd.api+json 186 | $array += $body.data 187 | Write-Host "Retrieved $($array.Count) IT Glue items" 188 | } while ($body.links.next) 189 | } 190 | return $array 191 | } 192 | 193 | $access_token = Get-GCITSAccessToken 194 | $SPHeaders = @{Authorization = "Bearer $access_token"} 195 | 196 | $list = Get-GCITSSharePointList -ListName $ListName 197 | $ITGOrgRegisterList = Get-GCITSSharePointList -ListName $ITGOrgRegisterListName 198 | 199 | if (!$list -and $ITGOrgRegisterList) {g 200 | Write-Output "List not found, creating List" 201 | # Initiate Columns 202 | $columnCollection = @() 203 | $columnCollection += New-GCITSSharePointColumn -Name UnifiSiteName -Type text -Indexed $true 204 | $columnCollection += New-GCITSSharePointColumn -Name ITGlue -Type lookup -Indexed $true -lookupListName $ITGOrgRegisterListName -lookupColumnName Title 205 | $List = New-GCITSSharePointList -Name $ListName -ColumnCollection $columnCollection 206 | } 207 | else { 208 | Write-Output "SharePoint list exists" 209 | } 210 | 211 | $headers = @{ 212 | "x-api-key" = $key 213 | } 214 | 215 | if ($ITGOrgRegisterList) { 216 | Write-Host "Getting IT Glue Configurations" 217 | #$configurations = Get-ITGlueItem -Resource configurations 218 | Write-Host "Getting IT Glue Organisations" 219 | #$orgs = Get-ITGlueItem -Resource organizations 220 | 221 | # Connect to UniFi Controller 222 | Invoke-RestMethod -Uri $UnifiBaseUri/login -Method POST -Body $uniFiCredentials -SessionVariable websession 223 | 224 | # Get Sites 225 | $sites = (Invoke-RestMethod -Uri "$UnifiBaseUri/self/sites" -WebSession $websession).data 226 | 227 | foreach ($site in $sites) { 228 | Write-Host "Matching $($site.desc)" 229 | # Check Name of site against IT Glue organisations. if no primary Match, check the devices themselves. 230 | $primarymatch = $orgs | Where-Object {$_.attributes.name -match $site.desc} | Select-Object -First 1 231 | if ($primarymatch) { 232 | Write-Host "Matched $($site.desc) (UniFi) with $($primarymatch.attributes.name) (IT Glue)" -ForegroundColor Green 233 | } 234 | else { 235 | Write-Host "Couldn't match by name. Attempting to match client devices from $($site.desc) by mac address with IT Glue Configurations" -ForegroundColor Yellow 236 | $matches = @() 237 | $clientDevices = Invoke-RestMethod -Uri "$UnifiBaseUri/s/$($site.name)/rest/user" -WebSession $websession 238 | 239 | foreach ($address in ($clientDevices.data.mac | Select-Object -first 200)) { 240 | $address = $address -replace ":", "-" 241 | $matches += $configurations | Where-Object {$_.attributes.'mac-address' -contains $address} 242 | } 243 | 244 | $primaryMatch = $matches.attributes | group-object organization-id | Select-Object -First 1 245 | 246 | if ($primaryMatch) { 247 | Write-Host "Matching $($site.desc) (UniFi) with $($primaryMatch.group[0].'organization-name') (IT Glue) with match count of $($primaryMatch.group.count)" -ForegroundColor Green 248 | $primaryMatch = $primarymatch.group[0] 249 | } 250 | } 251 | 252 | # If a match could be found, Create the SharePoint List Item 253 | if ($primaryMatch) { 254 | $ITGlueID = $primaryMatch.'organization-id' 255 | if (!$ITGlueID) { 256 | $ITGlueID = $primarymatch.id 257 | } 258 | $matchingOrg = $orgs | Where-Object {$_.id -eq $ITGlueID} 259 | $query = Get-GCITSSharePointListItem -ListId $ITGOrgRegisterList.id -Query "fields/ITGlueID eq '$itglueid'" 260 | if ($query) { 261 | $existingMatch = Get-GCITSSharePointListItem -ListId $List.id -Query "fields/UnifiSiteName eq '$($site.name)'" 262 | if (!$existingMatch) { 263 | $NewObject = @{ 264 | Title = $site.desc 265 | UnifiSiteName = $site.name 266 | ITGlueLookupId = $query.id 267 | } 268 | New-GCITSSharePointListItem -ListId $list.id -ItemObject $NewObject 269 | } 270 | else { 271 | Write-Host "$($site.desc) is added to the match register already" 272 | } 273 | } 274 | } 275 | else { 276 | Write-Host "Couldn't match $($site.desc)" -ForegroundColor Yellow 277 | $existingMatch = Get-GCITSSharePointListItem -ListId $List.id -Query "fields/UnifiSiteName eq '$($site.name)'" 278 | if (!$existingMatch) { 279 | $NewObject = @{ 280 | Title = $site.desc 281 | UnifiSiteName = $site.name 282 | } 283 | New-GCITSSharePointListItem -ListId $list.id -ItemObject $NewObject 284 | } 285 | else { 286 | Write-Host "$($site.desc) is added to the match register already" 287 | } 288 | } 289 | Write-Host "`n" 290 | } 291 | } 292 | else { 293 | Write-Host "Couldn't find list with name: $ITGOrgRegisterListName" 294 | } -------------------------------------------------------------------------------- /Office365/SharePoint/Enable-SharePointGraphApplication/README.md: -------------------------------------------------------------------------------- 1 | [Source](https://gcits.com/knowledge-base/create-a-sharepoint-application-for-the-microsoft-graph-via-powershell/ "Permalink to Create a SharePoint Application for the Microsoft Graph via PowerShell") 2 | 3 | # Create a SharePoint Application for the Microsoft Graph via PowerShell 4 | 5 | This script will create an application in your Azure AD tenant with permission to access SharePoint. The application will be created with the **Sites.Manage.All** App Only permission which will, among other things, allow it to create and edit lists. 6 | 7 | We will refer to this script in our other guides which require a SharePoint App for storing information in SharePoint lists and libraries. 8 | 9 | ### Prerequisites 10 | 11 | - This script will need to be run as a global admin in your tenant. 12 | - You'll need the Azure AD PowerShell module. If you don't have it, you can install it by opening PowerShell as an administrator and running: 13 | - 14 | 15 | ```powershell 16 | Install-Module AzureAD 17 | ``` 18 | 19 | ## How to run this script to create a SharePoint Application via PowerShell 20 | 21 | 1. Double click the script below to select it all, then copy it 22 | 2. Open Visual Studio Code, create new file and save it as a **.ps1** file 23 | 3. Update the values for the** $applicationName**, **$homePage** and **$appIdUri** variables. These don't need to be actual addresses, however the** $appIdUri** value needs to be unique within the tenant. 24 | ![Modify SharePoint Azure AD Application Variables][1] 25 | 4. Install the recommended PowerShell Extension in Visual Studio Code if you haven't already 26 | 5. Press **F5** to run the script 27 | 6. When prompted, logon as a global admin. Note that the login window usually appears behind Visual Studio Code 28 | ![Sign In To Azure AD as Global Admin When Prompted][2] 29 | 7. Wait for the script to run and perform its test call against the Microsoft Graph 30 | ![Wait For Application To Complete And Test][3] 31 | 8. Your **Tenant ID** and the **Client ID** and **Secret** for this application will be exported to a CSV file under C:tempAzureAdApp.csv. Retrieve the values and keep the CSV in a secure location, or delete it. 32 | ![Retrieve Application Tenant ID Client ID And Secret From CSV][4] 33 | 34 | ## PowerShell script to create Azure AD Application with permission to access SharePoint via Microsoft Graph 35 | 36 | ```powershell 37 | 38 | # This script needs to be run by an admin account in your Office 365 tenant 39 | # This script will create a SharePoint app in your organisation 40 | # It will export information about the application to a CSV located at C:temp. 41 | # The CSV will include the Client ID and Secret of the application, so keep it safe. 42 | 43 | # Confirm C:temp exists 44 | $temp = Test-Path -Path C:temp 45 | if ($temp) { 46 | #Write-Host "Path exists" 47 | } 48 | else { 49 | Write-Host "Creating Temp folder" 50 | New-Item -Path C:temp -ItemType directory 51 | } 52 | 53 | $applicationName = "GCITS SharePoint Application" 54 | 55 | # Change this to true if you would like to overwrite any existing applications with matching names. 56 | $removeExistingAppWithSameName = $true 57 | # Modify the homePage, appIdURI and logoutURI values to whatever valid URI you like. They don't need to be actual addresses. 58 | $homePage = "https://secure.gcits.com" 59 | $appIdURI = "https://secure.gcits.com/$((New-Guid).ToString())" 60 | $logoutURI = "https://portal.office.com" 61 | 62 | $URIForApplicationPermissionCall = "https://graph.microsoft.com/v1.0/sites/root" 63 | $ApplicationPermissions = "Sites.Manage.All" 64 | 65 | Function Add-ResourcePermission($requiredAccess, $exposedPermissions, $requiredAccesses, $permissionType) { 66 | foreach ($permission in $requiredAccesses.Trim().Split(" ")) { 67 | $reqPermission = $null 68 | $reqPermission = $exposedPermissions | Where-Object {$_.Value -contains $permission} 69 | Write-Host "Collected information for $($reqPermission.Value) of type $permissionType" -ForegroundColor Green 70 | $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess 71 | $resourceAccess.Type = $permissionType 72 | $resourceAccess.Id = $reqPermission.Id 73 | $requiredAccess.ResourceAccess.Add($resourceAccess) 74 | } 75 | } 76 | 77 | Function Get-RequiredPermissions($requiredDelegatedPermissions, $requiredApplicationPermissions, $reqsp) { 78 | $sp = $reqsp 79 | $appid = $sp.AppId 80 | $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess 81 | $requiredAccess.ResourceAppId = $appid 82 | $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess] 83 | if ($requiredDelegatedPermissions) { 84 | Add-ResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope" 85 | } 86 | if ($requiredApplicationPermissions) { 87 | Add-ResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role" 88 | } 89 | return $requiredAccess 90 | } 91 | Function New-AppKey ($fromDate, $durationInYears, $pw) { 92 | $endDate = $fromDate.AddYears($durationInYears) 93 | $keyId = (New-Guid).ToString() 94 | $key = New-Object Microsoft.Open.AzureAD.Model.PasswordCredential($null, $endDate, $keyId, $fromDate, $pw) 95 | return $key 96 | } 97 | 98 | Function Test-AppKey($fromDate, $durationInYears, $pw) { 99 | 100 | $testKey = New-AppKey -fromDate $fromDate -durationInYears $durationInYears -pw $pw 101 | while ($testKey.Value -match "+" -or $testKey.Value -match "/") { 102 | Write-Host "Secret contains + or / and may not authenticate correctly. Regenerating..." -ForegroundColor Yellow 103 | $pw = Initialize-AppKey 104 | $testKey = New-AppKey -fromDate $fromDate -durationInYears $durationInYears -pw $pw 105 | } 106 | Write-Host "Secret doesn't contain + or /. Continuing..." -ForegroundColor Green 107 | $key = $testKey 108 | 109 | return $key 110 | } 111 | 112 | Function Initialize-AppKey { 113 | $aesManaged = New-Object "System.Security.Cryptography.AesManaged" 114 | $aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC 115 | $aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros 116 | $aesManaged.BlockSize = 128 117 | $aesManaged.KeySize = 256 118 | $aesManaged.GenerateKey() 119 | return [System.Convert]::ToBase64String($aesManaged.Key) 120 | } 121 | function Confirm-MicrosoftGraphServicePrincipal { 122 | $graphsp = Get-AzureADServicePrincipal -SearchString "Microsoft Graph" 123 | if (!$graphsp) { 124 | $graphsp = Get-AzureADServicePrincipal -SearchString "Microsoft.Azure.AgregatorService" 125 | } 126 | if (!$graphsp) { 127 | Login-AzureRmAccount -Credential $credentials 128 | New-AzureRmADServicePrincipal -ApplicationId "00000003-0000-0000-c000-000000000000" 129 | $graphsp = Get-AzureADServicePrincipal -SearchString "Microsoft Graph" 130 | } 131 | return $graphsp 132 | } 133 | Write-Host "Connecting to Azure AD. The login window may appear behind Visual Studio Code." 134 | Connect-AzureAD 135 | 136 | Write-Host "Creating application in tenant: $((Get-AzureADTenantDetail).displayName)" 137 | 138 | # Check for the Microsoft Graph Service Principal. If it doesn't exist already, create it. 139 | $graphsp = Confirm-MicrosoftGraphServicePrincipal 140 | 141 | $existingapp = $null 142 | $existingapp = get-azureadapplication -SearchString $applicationName 143 | if ($existingapp -and $removeExistingAppWithSameName) { 144 | Remove-Azureadapplication -ObjectId $existingApp.objectId 145 | } 146 | 147 | # RSPS 148 | $rsps = @() 149 | if ($graphsp) { 150 | $rsps += $graphsp 151 | $tenant_id = (Get-AzureADTenantDetail).ObjectId 152 | $tenantName = (Get-AzureADTenantDetail).DisplayName 153 | 154 | # Add Required Resources Access (Microsoft Graph) 155 | $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess] 156 | $microsoftGraphRequiredPermissions = Get-RequiredPermissions -reqsp $graphsp -requiredApplicationPermissions $ApplicationPermissions -requiredDelegatedPermissions $DelegatedPermissions 157 | $requiredResourcesAccess.Add($microsoftGraphRequiredPermissions) 158 | 159 | # Get an application key 160 | $pw = Initialize-AppKey 161 | $fromDate = [System.DateTime]::Now 162 | $appKey = Test-AppKey -fromDate $fromDate -durationInYears 99 -pw $pw 163 | 164 | Write-Host "Creating the AAD application $applicationName" -ForegroundColor Blue 165 | $aadApplication = New-AzureADApplication -DisplayName $applicationName ` 166 | -HomePage $homePage ` 167 | -ReplyUrls $homePage ` 168 | -IdentifierUris $appIdURI ` 169 | -LogoutUrl $logoutURI ` 170 | -RequiredResourceAccess $requiredResourcesAccess ` 171 | -PasswordCredentials $appKey 172 | 173 | # Creating the Service Principal for the application 174 | $servicePrincipal = New-AzureADServicePrincipal -AppId $aadApplication.AppId 175 | 176 | Write-Host "Assigning Permissions" -ForegroundColor Yellow 177 | 178 | # Assign application permissions to the application 179 | foreach ($app in $requiredResourcesAccess) { 180 | $reqAppSP = $rsps | Where-Object {$_.appid -contains $app.ResourceAppId} 181 | Write-Host "Assigning Application permissions for $($reqAppSP.displayName)" -ForegroundColor DarkYellow 182 | foreach ($resource in $app.ResourceAccess) { 183 | if ($resource.Type -match "Role") { 184 | New-AzureADServiceAppRoleAssignment -ObjectId $serviceprincipal.ObjectId ` 185 | -PrincipalId $serviceprincipal.ObjectId -ResourceId $reqAppSP.ObjectId -Id $resource.Id 186 | } 187 | } 188 | } 189 | 190 | Write-Host "App Created" -ForegroundColor Green 191 | 192 | # Define parameters for Microsoft Graph access token retrieval 193 | $client_id = $aadApplication.AppId; 194 | $client_secret = $appkey.Value 195 | $tenant_id = (Get-AzureADTenantDetail).ObjectId 196 | $resource = "https://graph.microsoft.com" 197 | $authority = "https://login.microsoftonline.com/$tenant_id" 198 | $tokenEndpointUri = "$authority/oauth2/token" 199 | 200 | # Get the access token using grant type password for Delegated Permissions or grant type client_credentials for Application Permissions 201 | 202 | $content = "grant_type=client_credentials&client_id=$client_id&client_secret=$client_secret&resource=$resource" 203 | 204 | # Try to execute the API call 6 times 205 | 206 | $Stoploop = $false 207 | [int]$Retrycount = "0" 208 | do { 209 | try { 210 | $response = Invoke-RestMethod -Uri $tokenEndpointUri -Body $content -Method Post -UseBasicParsing 211 | Write-Host "Retrieved Access Token" -ForegroundColor Green 212 | # Assign access token 213 | $access_token = $response.access_token 214 | $body = $null 215 | 216 | $body = Invoke-RestMethod ` 217 | -Uri $UriForApplicationPermissionCall ` 218 | -Headers @{"Authorization" = "Bearer $access_token"} ` 219 | -ContentType "application/json" ` 220 | -Method GET ` 221 | 222 | Write-Host "Retrieved Graph content" -ForegroundColor Green 223 | $Stoploop = $true 224 | } 225 | catch { 226 | if ($Retrycount -gt 5) { 227 | Write-Host "Could not get Graph content after 6 retries." -ForegroundColor Red 228 | $Stoploop = $true 229 | } 230 | else { 231 | Write-Host "Could not get Graph content. Retrying in 5 seconds..." -ForegroundColor DarkYellow 232 | Start-Sleep -Seconds 5 233 | $Retrycount ++ 234 | } 235 | } 236 | } 237 | While ($Stoploop -eq $false) 238 | 239 | $appInfo = [pscustomobject][ordered]@{ 240 | ApplicationName = $ApplicationName 241 | TenantName = $tenantName 242 | TenantId = $tenant_id 243 | clientId = $client_id 244 | clientSecret = $client_secret 245 | ApplicationPermissions = $ApplicationPermissions 246 | } 247 | 248 | $AppInfo | Export-Csv C:tempAzureADApp.csv -Append -NoTypeInformation 249 | } 250 | else { 251 | Write-Host "Microsoft Graph Service Principal could not be found or created" -ForegroundColor Red 252 | } 253 | 254 | ``` 255 | 256 | ### About The Author 257 | 258 | ![Elliot Munro][5] 259 | 260 | #### [ Elliot Munro ][6] 261 | 262 | Elliot Munro is an Office 365 MCSA from the Gold Coast, Australia supporting hundreds of small businesses with GCITS. If you have an Office 365 or Azure issue that you'd like us to take a look at (or have a request for a useful script) send Elliot an email at [elliot@gcits.com][7] 263 | 264 | [1]: https://gcits.com/wp-content/uploads/ModifySharePointAzureADApplicationVariables-1030x348.png 265 | [2]: https://gcits.com/wp-content/uploads/SignInToAzureADAsGlobalAdminWhenPrompted-1030x319.png 266 | [3]: https://gcits.com/wp-content/uploads/WaitForApplicationToCompleteAndTest-1030x571.png 267 | [4]: https://gcits.com/wp-content/uploads/RetrieveApplicationTenantIDClientIDAndSecretFromCSV-1030x261.png 268 | [5]: https://gcits.com/wp-content/uploads/AAEAAQAAAAAAAA2QAAAAJDNlN2NmM2Y4LTU5YWYtNGRiNC1hMmI2LTBhMzdhZDVmNWUzNA-80x80.jpg 269 | [6]: https://gcits.com/author/elliotmunro/ 270 | [7]: mailto:elliot%40gcits.com 271 | --------------------------------------------------------------------------------