├── 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 Name | Active | Consumed | Unused |
|---|
| "
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 Name | Addresses | Assigned 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 Name | Active | Consumed | Unused |
|---|
| "
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 Name | Addresses | Assigned 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 |
--------------------------------------------------------------------------------
|