Added Files

This commit is contained in:
DistractADD
2021-07-06 13:16:46 +10:00
parent f2475a3bdd
commit cfddd4fad2
393 changed files with 65842 additions and 0 deletions

View File

@@ -0,0 +1,406 @@
<#
.SYNOPSIS
Get German Holidays via API and export them to CSV for Skype for Business (Online).
.DESCRIPTION
Get German Holidays via API and export them to CSV for Skype for Business (Online).
We use the German service feiertage-api.de to fetch the list, that is a great wrapper for the German Holidays published on Wikipedia.
.PARAMETER State
The german state
https://de.wikipedia.org/wiki/Feiertage_in_Deutschland
.PARAMETER Year
The Year to get and export from the API
Default is a acctual year (2019 while writing this)
.PARAMETER appendyear
Append the Year to the Holliday string?
The Default is YES
.PARAMETER Path
Specifies the path to the CSV file to export.
Default is Holidays.csv in your User Profile Home.
.EXAMPLE
.\Convert-HolidayFromApiToCsAutoAttendantHolidays.ps1
Get German Holidays via API and export them to CSV for Skype for Business (Online). We use all the defaults!
.EXAMPLE
.\Convert-HolidayFromApiToCsAutoAttendantHolidays.ps1 -State 'BY' -Year 2019 -path 'C:\Imports\Holidays.csv'
$bytes = [IO.File]::ReadAllBytes('C:\Imports\Holidays.csv')
Import-CsAutoAttendantHolidays -Identity 6283d913-8093-4951-8f46-c5912972002b -Input $bytes
Get German Holidays via API and export them to 'C:\Imports\Holidays.csv'. We then convert it into Bytes (how Skype for Business likes it) and import them into Skype for Business.
.NOTES
The Holiday break will begin at 5pm the day before the actual Holiday and will end the following day at 9am.
Please check if this match with your workflow and requirements!!!
Only German Holidays are supported by the script and the API
Only Skype for Business Online is tested! I use it with a native Microsoft Teams environment, but you need to use the Skype for Business Online PowerShell Module to import it.
If you like this, please support the feiertage-api.de project with a donation!
I switched from https://www.spiketime.de/feiertagapi to https://feiertage-api.de/api/ with the latest version. The output was a bit better on the other API, but this API seems to be actively maintained.
.LINK
https://feiertage-api.de
.LINK
https://www.spiketime.de/blog/spiketime-feiertag-api-feiertage-nach-bundeslandern/
.LINK
Import-CsAutoAttendantHolidays
.LINK
Export-CsAutoAttendantHolidays
.LINK
Invoke-RestMethod
.LINK
ConvertTo-Csv
.LINK
Set-Content
#>
[CmdletBinding(ConfirmImpact = 'None')]
param
(
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[ValidateSet('BW', 'BY', 'BE', 'BB', 'HB', 'HH', 'HE', 'MV', 'NI', 'NW', 'RP', 'SL', 'SN', 'ST', 'SH', 'TH', 'Baden-Württemberg', 'Baden-Wuerttemberg', 'Baden Württemberg', 'Baden Wuerttemberg', 'Bayern', 'Berlin', 'Brandenburg', 'Bremen', 'Hamburg', 'Hessen', 'Mecklenburg-Vorpommern', 'Mecklenburg Vorpommern', 'Niedersachsen', 'Nordrhein Westfalen', 'Nordrhein-Westfalen', 'Rheinland-Pfalz', 'Rheinland Pfalz', 'Saarland', 'Sachsen', 'Rheinland PfalzSachen-Anhalt', 'Schleswig-Holstein', 'Schleswig Holstein', 'Thüringen', 'Thueringen', IgnoreCase = $true)]
[string]
$State = 'HE',
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[int]
$Year = (Get-Date).ToString('yyyy'),
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[switch]
$appendyear,
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[Alias('Export', 'ExportCSV', 'CsvFile')]
[string]
$Path = "$env:USERPROFILE\Holidays.csv"
)
begin
{
#region Checks
if (-not $State)
{
$State = 'HE'
}
if (-not $Year)
{
$Year = (Get-Date).ToString('yyyy')
}
if (-not $Path)
{
$Path = "$env:USERPROFILE\Holidays.csv"
}
#endregion Checks
#region StateHandler
# More fuzzy state support
switch ($State)
{
'Baden-Württemberg'
{
$State = 'BW'
}
'Baden-Wuerttemberg'
{
$State = 'BW'
}
'Baden Württemberg'
{
$State = 'BW'
}
'Baden Wuerttemberg'
{
$State = 'BW'
}
'Bayern'
{
$State = 'BY'
}
'Berlin'
{
$State = 'BE'
}
'Brandenburg'
{
$State = 'BB'
}
'Bremen'
{
$State = 'HB'
}
'Hamburg'
{
$State = 'HH'
}
'Hessen'
{
$State = 'HE'
}
'Mecklenburg-Vorpommern'
{
$State = 'MV'
}
'Mecklenburg Vorpommern'
{
$State = 'MV'
}
'Niedersachsen'
{
$State = 'NI'
}
'Nordrhein-Westfalen'
{
$State = 'NW'
}
'Nordrhein Westfalen'
{
$State = 'NW'
}
'Rheinland-Pfalz'
{
$State = 'RP'
}
'Rheinland Pfalz'
{
$State = 'RP'
}
'Saarland'
{
$State = 'SL'
}
'Sachsen'
{
$State = 'SN'
}
'Sachen-Anhalt'
{
$State = 'ST'
}
'Sachen Anhalt'
{
$State = 'ST'
}
'Schleswig-Holstein'
{
$State = 'SH'
}
'Schleswig Holstein'
{
$State = 'SH'
}
'Thüringen'
{
$State = 'TH'
}
'Thueringen'
{
$State = 'TH'
}
default
{
# Good luck ;-)
$State = $State.ToUpper()
}
}
#endregion StateHandler
# Create a new Object for the CSV
$CsvDataObject = @()
# Build the URI
$FeiertagApiObjectUri = ('https://feiertage-api.de/api/?jahr=' + $Year + '&nur_land=' + ($State.ToUpper()))
}
process
{
# Splat the Parameters (Change the UserAgent if you want)
$paramInvokeRestMethod = @{
Method = 'Get'
Uri = $FeiertagApiObjectUri
ErrorAction = 'Stop'
WarningAction = 'Continue'
UserAgent = 'enaTecParser/1.0 (+http://www.enatec.io)'
}
# Use the API to get the List
try
{
$FeiertagApiObject = (Invoke-RestMethod @paramInvokeRestMethod)
}
catch
{
#region ErrorHandler
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
$info | Out-String | Write-Verbose
Write-Error -Message ($info.Exception) -ErrorAction Stop
# Only here to catch a global ErrorAction overwrite
exit 1
#endregion ErrorHandler
}
foreach ($SingleFeiertagApiObject in $FeiertagApiObject.PsObject.Properties)
{
# Create a new Object
$MemberObject = (New-Object -TypeName PSObject)
# Fill in the Values from the API Call
$MemberObject | Add-Member -NotePropertyName Name -NotePropertyValue $(if ($appendyear)
{
($SingleFeiertagApiObject.Name) + ' 2019'
}
else
{
($SingleFeiertagApiObject.Name)
}
)
$MemberObject | Add-Member -NotePropertyName StartDateTime1 -NotePropertyValue ((Get-Date -Date $SingleFeiertagApiObject.Value.datum).AddDays(-1).ToString('MM/dd/yyyy') + ' 17:00')
$MemberObject | Add-Member -NotePropertyName EndDateTime1 -NotePropertyValue ((Get-Date -Date $SingleFeiertagApiObject.Value.datum).AddDays(+1).ToString('MM/dd/yyyy') + ' 09:00')
# Add some useless rows to match the CSV object
$MemberObject | Add-Member -NotePropertyName StartDateTime2 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName EndDateTime2 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName StartDateTime3 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName EndDateTime3 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName StartDateTime4 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName EndDateTime4 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName StartDateTime5 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName EndDateTime5 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName StartDateTime6 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName EndDateTime6 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName StartDateTime7 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName EndDateTime7 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName StartDateTime8 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName EndDateTime8 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName StartDateTime9 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName EndDateTime9 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName StartDateTime10 -NotePropertyValue $null
$MemberObject | Add-Member -NotePropertyName EndDateTime10 -NotePropertyValue $null
# Add to the CSV object
$CsvDataObject += $MemberObject
# Cleanup
$MemberObject = $null
}
# Create the CSV Object (We remove the Quotes to make it a perfect fit) and save it
try
{
$paramSetContent = @{
Value = ($CsvDataObject | ConvertTo-Csv -UseCulture -NoTypeInformation | ForEach-Object {
$_.Replace('"', '')
})
Path = $Path
Force = $true
Confirm = $false
ErrorAction = 'Stop'
}
$null = (Set-Content @paramSetContent)
}
catch
{
#region ErrorHandler
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
$info | Out-String | Write-Verbose
Write-Error -Message ($info.Exception) -ErrorAction Stop
# Only here to catch a global ErrorAction overwrite
exit 1
#endregion ErrorHandler
}
}
end
{
Write-Verbose -Message ((Get-Content -Path $Path).ToString())
#region Cleanup
$State = $null
$Year = $null
$appendyear = $null
$Path = $null
$CsvDataObject = $null
$FeiertagApiObjectUri = $null
$paramInvokeRestMethod = $null
$FeiertagApiObject = $null
$info = $null
$MemberObject = $null
$paramSetContent = $null
#endregion Cleanup
}
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER

View File

@@ -0,0 +1,194 @@
function ConvertFrom-SafeLinksURL
{
<#
.SYNOPSIS
Decode a ATP SafeLinks URL
.DESCRIPTION
Decode a Office 365 Advanced Threat Protection SafeLinks URL
.PARAMETER SafeLinksURL
The ATP SafeLinks URL that you want to decode into original URL
.EXAMPLE
PS C:\> ConvertFrom-SafeLinksURL -SafeLinksURL 'https://eur03.safelinks.protection.outlook.com/?url=https%3A%2F%2Fhochwald.net%2F&data=04%7C01%7Cjoerg%40hochwald.net%7C6944b67827e54648125508d884babf16%7Cb768b3c4dc4b445c94c0388882f966fb%7C0%7C0%7C637405284900251734%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&sdata=qPb0a6MdRNuAzMIyLPlQ9iHPAufxNRywP2kKi%2FIHs%2FA%3D&reserved=0'
This will decode the given URL and return the original URL (https://hochwald.net/)
.EXAMPLE
PS C:\> ConvertFrom-SafeLinksURL -SafeLinksURL 'https://jhochwald.com'
This will fail, the provided string is not a valid ATP SafeLink URL
.EXAMPLE
PS C:\> ConvertFrom-SafeLinksURL -SafeLinksURL 'https://eur03.safelinks.protection.outlook.com/?url=https%3A%2F%2Fhochwald.net%2F&reserved=0'
This will fail, the provided string is not a valid ATP SafeLink URL
.NOTES
Basic PowerShell function to replace an outdated Ruby script
There is also a great web based solution for this approach: http://www.o365atp.com
.LINK
https://gist.github.com/jhochwald/8c9a3ef448058502ed184512e586815f
.LINK
http://www.o365atp.com
.LINK
https://products.office.com/en-us/exchange/online-email-threat-protection
#>
[CmdletBinding(ConfirmImpact = 'None')]
[OutputType([string])]
param
(
[Parameter(Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName,
Position = 0,
HelpMessage = 'The ATP SafeLinks URL that you want to decode into original URL')]
[ValidateNotNullOrEmpty()]
[Alias('SafeLink')]
[uri]
$SafeLinksURL
)
begin
{
#region Defaults
$STP = 'Stop'
#endregion Defaults
try
{
# Load the Web Assembly to decode the URL
$null = (Add-Type -AssemblyName System.Web)
}
catch
{
# Get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
$paramWriteError = @{
Message = $e.Exception.Message
ErrorAction = $STP
Exception = $e.Exception
TargetObject = $e.CategoryInfo.TargetName
}
Write-Error @paramWriteError
}
}
process
{
try
{
# Create a new Object with the decoded URL, we use the default Web Assembly here
$OriginalURL = [Web.HttpUtility]::UrlDecode($SafeLinksURL)
# Check the URL object
if ($OriginalURL -match '.safelinks.protection.outlook.com\/\?url=.+&data=')
{
$OriginalURL = $Matches[$Matches.Count - 1]
# The default value (&) is used to provide the data string
$OriginalURL = (($OriginalURL -Split '\?url=')[1] -Split '&data=')[0]
}
elseif ($OriginalURL -match '.safelinks.protection.outlook.com\/\?url=.+&amp;data=')
{
$OriginalURL = $Matches[$Matches.Count - 1]
# Does the object use &amp; instead of & to provide the data string
$OriginalURL = (($OriginalURL -Split '\?url=')[1] -Split '&amp;data=')[0]
}
else
{
$paramWriteError = @{
Exception = 'Invalid SafeLinks URL provided'
Message = 'The URL provided die NOT look like a valid Office 365 Advanced Threat Protection SafeLink URL'
Category = 'InvalidData'
TargetObject = $SafeLinksURL
RecommendedAction = 'Check the provided URL'
ErrorAction = $STP
}
Write-Error @paramWriteError
}
}
catch
{
# Get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
$paramWriteError = @{
Message = $e.Exception.Message
ErrorAction = $STP
Exception = $e.Exception
TargetObject = $e.CategoryInfo.TargetName
}
Write-Error @paramWriteError
}
}
end
{
[string]$OriginalURL
}
}
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER

View File

@@ -0,0 +1,544 @@
#requires -Version 2.0 -Modules MSOnline
<#
PowerShell Core (PWSH) is not supported, at least not yet
# Easy way to install the MSOL Module, requires PowerShell 5 or PSGet
Install-Module -Name MSOnline
I still use the old (MSOL) module, cause it works best at the moment.
I might convert more and more to the newer modules
#>
#region HelperFunctions
function Get-ServicePlanFriendlyList
{
<#
.SYNOPSIS
A hash table of Office 365 Service Plans and there Human understandable description
.DESCRIPTION
Helper function to make the Office 365 Service Plans as returned from scripts understandable for humans
.EXAMPLE
PS C:\> Get-ServicePlanFriendlyList
.NOTES
Internal Helper
Version: 2.1 - Latest List
Author: Joerg Hochwald <http://jhochwald.com>
License: The 3-Clause BSD License <https://opensource.org/licenses/BSD-3-Clause>
#>
[CmdletBinding()]
[OutputType([hashtable])]
param ()
begin
{
# Cleanup
$ServicePlanFriendlyList = $null
}
process
{
# Build the HashTable
$ServicePlanFriendlyList = @{
'AAD_BASIC' = 'Azure Active Directory Basic'
'AAD_PREMIUM' = 'Azure Active Directory Premium'
'MFA_PREMIUM' = 'Azure Multi-Factor Authentication'
'RMS_S_ENTERPRISE' = 'Azure Information Protection'
'RMS_S_ENTERPRISE_GOV' = 'Azure Information Protection for Government'
'SHAREPOINT_DUET_EDU' = 'Duet Online for Academics'
'SHAREPOINT_DUET_GOV' = 'Duet Online for Government'
'EXCHANGE_S_STANDARD' = 'Exchange Online (Plan 1)'
'EXCHANGE_S_STANDARD_GOV' = 'Exchange Online (Plan 1 for Government)'
'EXCHANGE_S_ENTERPRISE' = 'Exchange Online (Plan 2)'
'EXCHANGE_S_ENTERPRISE_GOV' = 'Exchange Online (Plan 2 for Government)'
'EXCHANGE_S_ARCHIVE' = 'Exchange Online Archiving'
'EXCHANGE_S_ARCHIVE_GOV' = 'Exchange Online Archiving for Government'
'EXCHANGE_S_DESKLESS' = 'Exchange Online Kiosk'
'EXCHANGE_S_DESKLESS_GOV' = 'Exchange Online Kiosk for Government'
'EOP_ENTERPRISE' = 'Exchange Online Protection'
'EOP_ENTERPRISE_GOV' = 'Exchange Online Protection for Government'
'INTUNE_A' = 'Intune'
'MCOIMP' = 'Skype for Business Online (formerly Lync Online (Plan 1)'
'MCOIMP_GOV' = 'Skype for Business Online (Plan 1 for Government)'
'MCOSTANDARD' = 'Skype for Business Online (Plan 2)'
'MCOSTANDARD_GOV' = 'Skype for Business Online (Plan 2 for Government)'
'MCOVOICECONF' = 'Skype for Business Online (Plan 3)'
'MCOVOICECONF_GOV' = 'Skype for Business Online (Plan 3 for Government)'
'CRMENTERPRISE' = 'Microsoft Dynamics CRM Online Enterprise'
'CRMSTANDARD_GCC' = 'Microsoft Dynamics CRM Online Government Professional'
'CRMSTANDARD' = 'Microsoft Dynamics CRM Online Professional'
'DMENTERPRISE' = 'Microsoft Dynamics Marketing Online Enterprise'
'MDM_SALES_COLLABORATION' = 'Microsoft Dynamics Marketing Sales Collaboration'
'SQL_IS_SSIM' = 'Microsoft Power BI Information Services Plan 1'
'BI_AZURE_P1' = 'Microsoft Power BI Reporting and Analytics Plan 1'
'BI_AZURE_P2' = 'Microsoft Power BI Reporting and Analytics Plan 2'
'NBENTERPRISE' = 'Microsoft Social Listening Enterprise'
'NBPROFESSIONALFORCRM' = 'Microsoft Social Listening Professional'
'INTUNE_O365' = 'Mobile Device Management for Office 365'
'OFFICE_BUSINESS' = 'Office 365 Business'
'OFFICESUBSCRIPTION' = 'Office 365 ProPlus'
'OFFICESUBSCRIPTION_GOV' = 'Office 365 ProPlus for Government'
'OFFICE_PRO_PLUS_SUBSCRIPTION_SMBIZ' = 'Office 365 Small Business Subscription'
'SHAREPOINTWAC' = 'Office Online'
'SHAREPOINTWAC_DEVELOPER' = 'Office Online Developer'
'SHAREPOINTWAC_EDU' = 'Office Online EDU'
'SHAREPOINTWAC_DEVELOPER_GOV' = 'Office Online for Government Developer'
'SHAREPOINTWAC_GOV' = 'Office Online for Government'
'ONEDRIVESTANDARD' = 'OneDrive for Business (Plan 1)'
'ONEDRIVESTANDARD_GOV' = 'OneDrive for Business (Plan 1 for Government)'
'ONEDRIVELITE' = 'OneDrive for Business Lite'
'PARATURE_ENTERPRISE' = 'Parature Enterprise'
'PARATURE_ENTERPRISE_GOV' = 'Parature Enterprise for Government'
'BI_AZURE_P0' = 'Power BI'
'PROJECT_ESSENTIALS' = 'Project Lite'
'PROJECT_ESSENTIALS_GOV' = 'Project Lite for Government'
'SHAREPOINT_PROJECT' = 'Project Online'
'SHAREPOINT_PROJECT_EDU' = 'Project Online for Academics'
'SHAREPOINT_PROJECT_GOV' = 'Project Online for Government'
'PROJECT_CLIENT_SUBSCRIPTION' = 'Project Pro for Office 365'
'PROJECT_CLIENT_SUBSCRIPTION_GOV' = 'Project Pro for Office 365 for Government'
'SHAREPOINTSTANDARD' = 'SharePoint Online (Plan 1)'
'SHAREPOINTSTANDARD_EDU' = 'SharePoint Online (Plan 1 for Academics)'
'SHAREPOINTSTANDARD_GOV' = 'SharePoint Online (Plan 1 for Government)'
'SHAREPOINTENTERPRISE' = 'SharePoint Online (Plan 2)'
'SHAREPOINTENTERPRISE_EDU' = 'SharePoint Online (Plan 2 for Academics)'
'SHAREPOINTENTERPRISE_GOV' = 'SharePoint Online (Plan 2 for Government)'
'SHAREPOINT_S_DEVELOPER' = 'SharePoint Online for Developer'
'SHAREPOINT_S_DEVELOPER_GOV' = 'SharePoint Online for Government Developer'
'SHAREPOINTDESKLESS' = 'SharePoint Online Kiosk'
'SHAREPOINTDESKLESS_GOV' = 'SharePoint Online Kiosk for Government'
'VISIO_CLIENT_SUBSCRIPTION' = 'Visio Pro for Office 365'
'VISIO_CLIENT_SUBSCRIPTION_GOV' = 'Visio Pro for Office 365 for Government'
'YAMMER_ENTERPRISE' = 'Yammer Enterprise'
'YAMMER_EDU' = 'Yammer for Academic For Academics'
'FLOW_O365_P2' = 'Flow for Office 365 P2'
'POWERAPPS_O365_P2' = 'PowerApps for Office 365 P2'
'TEAMS1' = 'Microsoft Teams'
'PROJECTWORKMANAGEMENT' = 'Microsoft Planner'
'SWAY' = 'SWAY'
'Deskless' = 'Microsoft StaffHub'
'FLOW_O365_P3' = 'Flow for Office 365 P3'
'POWERAPPS_O365_P3' = 'PowerApps for Office 365 P3'
'ADALLOM_S_O365' = 'Office 365 Advanced Security Management'
'EQUIVIO_ANALYTICS' = 'Office 365 Advanced eDiscovery'
'LOCKBOX_ENTERPRISE' = 'Customer Lockbox'
'EXCHANGE_ANALYTICS' = 'Microsoft MyAnalytics'
'ATP_ENTERPRISE' = 'Exchange Online Advanced Threat Protection (These licenses do not need to be individually assigned)'
'MCOEV' = 'Teams/Skype for Business Cloud PBX'
'MCOMEETADV' = 'Teams/Skype for Business PSTN Conferencing'
}
}
end
{
# Dump
$ServicePlanFriendlyList
}
}
function Get-SkuPartNumberFriendlyNameList
{
<#
.SYNOPSIS
A Hashtable of Office 365 SKUs and there Human understandable description
.DESCRIPTION
Helper function to make the Office 365 SKUs as returned from scripts understandable for humans
.EXAMPLE
PS C:\> Get-SkuPartNumberFriendlyNameList
.NOTES
Internal Helper
Version: 2.1 - Latest List
Author: Joerg Hochwald <http://jhochwald.com>
License: The 3-Clause BSD License <https://opensource.org/licenses/BSD-3-Clause>
#>
[CmdletBinding()]
[OutputType([hashtable])]
param ()
begin
{
# Cleanup
$SkuPartNumberFriendlyNameList = $null
}
process
{
# Build the HashTable
$SkuPartNumberFriendlyNameList = @{
'AAD_BASIC' = 'Azure Active Directory Basic'
'AAD_PREMIUM' = 'Azure Active Directory Premium'
'RIGHTSMANAGEMENT' = 'Azure Active Directory Rights'
'RIGHTSMANAGEMENT_FACULTY' = 'Azure Active Directory Rights for Faculty'
'RIGHTSMANAGEMENT_GOV' = 'Azure Active Directory Rights for Government'
'RIGHTSMANAGEMENT_STUDENT' = 'Azure Active Directory Rights for Students'
'MFA_STANDALONE' = 'Azure Multi-Factor Authentication Premium Standalone'
'EMS' = 'Microsoft Enterprise Mobility + Security Suite'
'EXCHANGESTANDARD_FACULTY' = 'Exchange (Plan 1 for Faculty)'
'EXCHANGESTANDARD_STUDENT' = 'Exchange (Plan 1 for Students)'
'EXCHANGEENTERPRISE_FACULTY' = 'Exchange (Plan 2 for Faculty)'
'EXCHANGEENTERPRISE_STUDENT' = 'Exchange (Plan 2 for Students)'
'EXCHANGEARCHIVE' = 'Exchange Archiving'
'EXCHANGEARCHIVE_FACULTY' = 'Exchange Archiving for Faculty'
'EXCHANGEARCHIVE_GOV' = 'Exchange Archiving for Government'
'EXCHANGEARCHIVE_STUDENT' = 'Exchange Archiving for Students'
'EXCHANGESTANDARD_GOV' = 'Exchange for Government (Plan 1G)'
'EXCHANGEENTERPRISE_GOV' = 'Exchange for Government (Plan 2G)'
'EXCHANGEDESKLESS' = 'Exchange Kiosk'
'EXCHANGEDESKLESS_GOV' = 'Exchange Kiosk for Government'
'EXCHANGESTANDARD' = 'Exchange Plan 1'
'EXCHANGEENTERPRISE' = 'Exchange Plan 2'
'EOP_ENTERPRISE_FACULTY' = 'Exchange Protection for Faculty'
'EOP_ENTERPRISE_GOV' = 'Exchange Protection for Government'
'EOP_ENTERPRISE_STUDENT' = 'Exchange Protection for Student'
'EXCHANGE_ONLINE_WITH_ONEDRIVE_LITE' = 'Exchange with OneDrive for Business'
'INTUNE_A' = 'Intune'
'MCOIMP_FACULTY' = 'Lync (Plan 1 for Faculty)'
'MCOIMP_STUDENT' = 'Lync (Plan 1 for Students)'
'MCOSTANDARD_FACULTY' = 'Lync (Plan 2 for Faculty)'
'MCOSTANDARD_STUDENT' = 'Lync (Plan 2 for Students)'
'MCOVOICECONF' = 'Lync (Plan 3)'
'MCOIMP_GOV' = 'Lync for Government (Plan 1G)'
'MCOSTANDARD_GOV' = 'Lync for Government (Plan 2G)'
'MCOVOICECONF_GOV' = 'Lync for Government (Plan 3G)'
'MCOINTERNAL' = 'Lync Internal Incubation and Corp to Cloud'
'MCOIMP' = 'Skype Plan 1'
'MCOSTANDARD' = 'Skype Plan 2'
'MCOVOICECONF_FACULTY' = 'Lync Plan 3 for Faculty'
'MCOVOICECONF_STUDENT' = 'Lync Plan 3 for Students'
'CRMENTERPRISE' = 'Microsoft Dynamics CRM Online Enterprise'
'CRMSTANDARD_GCC' = 'Microsoft Dynamics CRM Online Government Professional'
'CRMSTANDARD' = 'Microsoft Dynamics CRM Online Professional'
'DMENTERPRISE' = 'Microsoft Dynamics Marketing Online Enterprise'
'INTUNE_O365_STANDALONE' = 'Mobile Device Management for Office 365'
'OFFICE_BASIC' = 'Office 365 Basic'
'O365_BUSINESS' = 'Office 365 Business'
'O365_BUSINESS_ESSENTIALS' = 'Office 365 Business Essentials'
'O365_BUSINESS_PREMIUM' = 'Office 365 Business Premium'
'DEVELOPERPACK' = 'Office 365 Developer'
'DEVELOPERPACK_GOV' = 'Office 365 Developer for Government'
'EDUPACK_FACULTY' = 'Office 365 Education for Faculty'
'EDUPACK_STUDENT' = 'Office 365 Education for Students'
'EOP_ENTERPRISE' = 'Office 365 Exchange Protection Enterprise'
'EOP_ENTERPRISE_PREMIUM' = 'Office 365 Exchange Protection Premium'
'STANDARDPACK_GOV' = 'Office 365 for Government (Plan G1)'
'STANDARDWOFFPACK_GOV' = 'Office 365 for Government (Plan G2)'
'ENTERPRISEPACK_GOV' = 'Office 365 for Government (Plan G3)'
'ENTERPRISEWITHSCAL_GOV' = 'Office 365 for Government (Plan G4)'
'DESKLESSPACK_GOV' = 'Office 365 for Government (Plan F1G)'
'STANDARDPACK_FACULTY' = 'Office 365 Plan A1 for Faculty'
'STANDARDPACK_STUDENT' = 'Office 365 Plan A1 for Students'
'STANDARDWOFFPACK_FACULTY' = 'Office 365 Plan A2 for Faculty'
'STANDARDWOFFPACK_STUDENT' = 'Office 365 Plan A2 for Students'
'ENTERPRISEPACK_FACULTY' = 'Office 365 Plan A3 for Faculty'
'ENTERPRISEPACK_STUDENT' = 'Office 365 Plan A3 for Students'
'ENTERPRISEWITHSCAL_FACULTY' = 'Office 365 Plan A4 for Faculty'
'ENTERPRISEWITHSCAL_STUDENT' = 'Office 365 Plan A4 for Students'
'STANDARDPACK' = 'Office 365 Plan E1'
'STANDARDWOFFPACK' = 'Office 365 Plan E2'
'ENTERPRISEPACK' = 'Office 365 Plan E3'
'ENTERPRISEWITHSCAL' = 'Office 365 Plan E4'
'DESKLESSPACK' = 'Office 365 Plan F1'
'DESKLESSPACK_YAMMER' = 'Office 365 Plan F1 with Yammer'
'OFFICESUBSCRIPTION' = 'Office Professional Plus'
'OFFICESUBSCRIPTION_FACULTY' = 'Office Professional Plus for Faculty'
'OFFICESUBSCRIPTION_GOV' = 'Office Professional Plus for Government'
'OFFICESUBSCRIPTION_STUDENT' = 'Office Professional Plus for Students'
'WACSHAREPOINTSTD_FACULTY' = 'Office Web Apps (Plan 1 For Faculty)'
'WACSHAREPOINTSTD_STUDENT' = 'Office Web Apps (Plan 1 For Students)'
'WACSHAREPOINTSTD_GOV' = 'Office Web Apps (Plan 1G for Government)'
'WACSHAREPOINTENT_FACULTY' = 'Office Web Apps (Plan 2 For Faculty)'
'WACSHAREPOINTENT_STUDENT' = 'Office Web Apps (Plan 2 For Students)'
'WACSHAREPOINTENT_GOV' = 'Office Web Apps (Plan 2G for Government)'
'WACSHAREPOINTSTD' = 'Office Web Apps with SharePoint Plan 1'
'WACSHAREPOINTENT' = 'Office Web Apps with SharePoint Plan 2'
'ONEDRIVESTANDARD' = 'OneDrive for Business'
'ONEDRIVESTANDARD_GOV' = 'OneDrive for Business for Government (Plan 1G)'
'WACONEDRIVESTANDARD' = 'OneDrive for Business with Office Web Apps'
'WACONEDRIVESTANDARD_GOV' = 'OneDrive for Business with Office Web Apps for Government'
'PARATURE_ENTERPRISE' = 'Parature Enterprise'
'PARATURE_ENTERPRISE_GOV' = 'Parature Enterprise for Government'
'POWER_BI_STANDARD' = 'Power BI'
'POWER_BI_STANDALONE' = 'Power BI for Office 365'
'POWER_BI_STANDALONE_FACULTY' = 'Power BI for Office 365 for Faculty'
'POWER_BI_STANDALONE_STUDENT' = 'Power BI for Office 365 for Students'
'PROJECTESSENTIALS' = 'Project Essentials'
'PROJECTESSENTIALS_GOV' = 'Project Essentials for Government'
'PROJECTONLINE_PLAN_1' = 'Project Plan 1'
'PROJECTONLINE_PLAN_1_FACULTY' = 'Project Plan 1 for Faculty'
'PROJECTONLINE_PLAN_1_GOV' = 'Project Plan 1for Government'
'PROJECTONLINE_PLAN_1_STUDENT' = 'Project Plan 1 for Students'
'PROJECTONLINE_PLAN_2' = 'Project Plan 2'
'PROJECTONLINE_PLAN_2_FACULTY' = 'Project Plan 2 for Faculty'
'PROJECTONLINE_PLAN_2_GOV' = 'Project Plan 2 for Government'
'PROJECTONLINE_PLAN_2_STUDENT' = 'Project Plan 2 for Students'
'PROJECTCLIENT' = 'Project Pro for Office 365'
'PROJECTCLIENT_FACULTY' = 'Project Pro for Office 365 for Faculty'
'PROJECTCLIENT_GOV' = 'Project Pro for Office 365 for Government'
'PROJECTCLIENT_STUDENT' = 'Project Pro for Office 365 for Students'
'SHAREPOINTSTANDARD_FACULTY' = 'SharePoint (Plan 1 for Faculty)'
'SHAREPOINTSTANDARD_STUDENT' = 'SharePoint (Plan 1 for Students)'
'SHAREPOINTSTANDARD_YAMMER' = 'SharePoint (Plan 1 with Yammer)'
'SHAREPOINTENTERPRISE_FACULTY' = 'SharePoint (Plan 2 for Faculty)'
'SHAREPOINTENTERPRISE_STUDENT' = 'SharePoint (Plan 2 for Students)'
'SHAREPOINTENTERPRISE_YAMMER' = 'SharePoint (Plan 2 with Yammer)'
'SHAREPOINTSTANDARD_GOV' = 'SharePoint for Government (Plan 1G)'
'SHAREPOINTENTERPRISE_GOV' = 'SharePoint for Government (Plan 2G)'
'SHAREPOINTDESKLESS' = 'SharePoint Kiosk'
'SHAREPOINTSTANDARD' = 'SharePoint Plan 1'
'SHAREPOINTENTERPRISE' = 'SharePoint Plan 2'
'SMB_BUSINESS' = 'SMB Business'
'SMB_BUSINESS_ESSENTIALS' = 'SMB Business Essentials'
'SMB_BUSINESS_PREMIUM' = 'SMB Business Premium'
'VISIOCLIENT' = 'Visio Pro for Office 365'
'VISIOCLIENT_FACULTY' = 'Visio Pro for Office 365 for Faculty'
'VISIOCLIENT_GOV' = 'Visio Pro for Office 365 for Government'
'VISIOCLIENT_STUDENT' = 'Visio Pro for Office 365 for Students'
'YAMMER_ENTERPRISE_STANDALONE' = 'Yammer Enterprise Standalone'
'RIGHTSMANAGEMENT_ADHOC' = 'Azure Rights Management Service'
'ENTERPRISEPREMIUM' = 'Office 365 Enterprise E5'
}
}
end
{
# Dump
$SkuPartNumberFriendlyNameList
}
}
#endregion HelperFunctions
# region MainFunctions
function Convert-MsolServicePlanName
{
<#
.SYNOPSIS
Convert between the Office 365 ServicePlanName from Get-MsolAccountSku to a human understandable format. It works in both directions
.DESCRIPTION
Coverts the Office 365 ServicePlanName from Get-MsolAccountSku to a human understandable format (as viewed in the Office 365 Admin Portal).
I use this for reporting and other licence related scripts to make the output understandable for the user.
.PARAMETER ServicePlanName
ServicePlanName from PowerShell query, e.g. Intune
.PARAMETER ServicePlanFriendlyName
Human underandable description of a plan, e.g. Azure Multi-Factor Authentication
.EXAMPLE
# Get all availible Services in a Human understanable Format
PS> Get-MsolAccountSku | Select-Object -Property @{
Name = 'ServicePlanName'
Expression = {
$_.ServiceStatus.ServicePlan.ServiceName
}
} | ForEach-Object -Process {
$_.ServicePlanName
} | Convert-MsolServicePlanName
Teams/Skype for Business Cloud PBX
Power BI
Teams/Skype for Business PSTN Conferencing
Microsoft StaffHub
Microsoft Teams
Office Online
Microsoft Planner
SWAY
Mobile Device Management for Office 365
Yammer Enterprise
Skype for Business Online (Plan 2)
SharePoint Online (Plan 1)
Exchange Online (Plan 1)
.EXAMPLE
# Convert a SKU Service Name to a human understandable format
PS> Convert-MsolServicePlanName -ServicePlanName "AAD_BASIC"
Azure Active Directory Basic
.EXAMPLE
# Convert a human understandable format to SKU Service Name, to use in scripts
PS> Convert-MsolServicePlanName -ServicePlanFriendlyName "Azure Multi-Factor Authentication"
MFA_PREMIUM
.NOTES
Version: 2.1 - Latest List
Author: Joerg Hochwald <http://jhochwald.com>
License: The 3-Clause BSD License <https://opensource.org/licenses/BSD-3-Clause>
#>
[CmdletBinding(DefaultParameterSetName = 'ByFriendlyName')]
param
(
[Parameter(ParameterSetName = 'ByServicePlanName',
Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
Position = 0,
HelpMessage = 'ServicePlanName from PowerShell query, e.g. AAD_BASIC')]
[Alias('Name')]
[string]
$ServicePlanName,
[Parameter(ParameterSetName = 'ByFriendlyName',
Mandatory = $true,
Position = 0,
HelpMessage = 'Friendly name of a plan, e.g. Exchange Online (Plan 1)')]
[Alias('FriendlyName')]
[string]
$ServicePlanFriendlyName
)
process
{
# Moved to a dedicated function
$ServicePlanFriendlyList = (Get-ServicePlanFriendlyList)
if ($ServicePlanName)
{
$ServicePlanFriendlyList["$ServicePlanName"]
}
if ($ServicePlanFriendlyName)
{
($ServicePlanFriendlyList.GetEnumerator() | Where-Object -FilterScript {
$_.Value -eq "$ServicePlanFriendlyName"
}).Name
}
}
}
function Convert-MsolAccountSkuName
{
<#
.SYNOPSIS
Convert between the Office 365 SKU Name from Get-MsolAccountSku to a human understandable format. It works in both directions
.DESCRIPTION
Coverts the Office 365 SKU Name from Get-MsolAccountSku to a human understandable format (as viewed in the Office 365 Admin Portal).
I use this for reporting and other licence related scripts to make the output understandable for the user.
.PARAMETER SkuPartNumber
ServicePlanName from scripted query, e.g. EXCHANGEENTERPRISE
.PARAMETER SkuPartNumberFriendlyName
human understandable format of a plan, e.g. Exchange Plan 1
.EXAMPLE
# Get a human understandable output for all existing SKUs
Get-MsolAccountSku | Select-Object -ExpandProperty SkuPartNumber | Convert-MsolAccountSkuName
Power BI
Office 365 Plan E1
.EXAMPLE
# Get the human understandable description from a SKU Number/Name
Convert-MsolAccountSkuName -SkuPartNumber 'EXCHANGEENTERPRISE'
Exchange Plan 2
.EXAMPLE
# Get the script compatible description from an human understandable format
Convert-MsolAccountSkuName -SkuPartNumberFriendlyName 'Exchange Plan 1'
EXCHANGESTANDARD
.NOTES
Version: 2.1 - Latest List
Author: Joerg Hochwald <http://jhochwald.com>
License: The 3-Clause BSD License <https://opensource.org/licenses/BSD-3-Clause>
#>
[CmdletBinding(DefaultParameterSetName = 'BySkuFriendlyName')]
param
(
[Parameter(ParameterSetName = 'BySkuPartNumber',
Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
Position = 0,
HelpMessage = 'ServicePlanName from PowerShell query, e.g. "EXCHANGEENTERPRISE"')]
[Alias('PartNumber')]
[string]
$SkuPartNumber,
[Parameter(ParameterSetName = 'BySkuFriendlyName',
Mandatory = $true,
Position = 0,
HelpMessage = 'Friendly name of a plan, e.g. "Exchange Plan 1"')]
[Alias('FriendlyName')]
[string]
$SkuPartNumberFriendlyName
)
process
{
# Moved to a dedicated function
$SkuPartNumberFriendlyNameList = (Get-SkuPartNumberFriendlyNameList)
if ($SkuPartNumber)
{
$SkuPartNumberFriendlyNameList["$SkuPartNumber"]
}
if ($SkuPartNumberFriendlyName)
{
($SkuPartNumberFriendlyNameList.GetEnumerator() | Where-Object -FilterScript {
$_.Value -eq "$SkuPartNumberFriendlyName"
}).Name
}
}
}
#endregion MainFunctions
#region Info
Write-Output -InputObject 'MsolServicePlanName:'
Get-MsolAccountSku | Select-Object -Property @{
Name = 'ServicePlanName'
Expression = {
$_.ServiceStatus.ServicePlan.ServiceName
}
} | ForEach-Object -Process {
$_.ServicePlanName
} | Convert-MsolServicePlanName
Write-Output -InputObject ''
Write-Output -InputObject 'MsolAccountSkuName:'
Get-MsolAccountSku | Select-Object -ExpandProperty SkuPartNumber | Convert-MsolAccountSkuName
#endregion Info
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER

View File

@@ -0,0 +1,262 @@
#requires -Version 3.0 -Modules MSOnline
<#
.SYNOPSIS
Find all direct assigned Microsoft 365 licenses
.DESCRIPTION
Find all direct assigned Microsoft 365 licenses.
you can display the licenses or export the info to a CSV file
.PARAMETER Export
Export the information to CSV.
.PARAMETER Display
Use Out-GridView to display the information.
.PARAMETER Path
Path to the CSV
.PARAMETER MsolName
The MSOL short name.
e.g. contoso
.EXAMPLE
PS C:\> .\Find-AdminAssignedLicenses.ps1 -MsolName 'contoso' -Display
Find all direct assigned Microsoft 365 licenses and show it via Out-GridView
.EXAMPLE
PS C:\> .\Find-AdminAssignedLicenses.ps1 -MsolName 'contoso' -Export
Find all direct assigned Microsoft 365 licenses and export it to the default CSV
.EXAMPLE
PS C:\> .\Find-AdminAssignedLicenses.ps1 -MsolName 'contoso' -Export -Path 'C:\scripts\PowerShell\export\AdminAssignedLicenses.csv'
Find all direct assigned Microsoft 365 licenses and export it to a given CSV
.NOTES
The next version will bring another switch to dump the info to the console. Based on a request of a customer.
This script based on the idea of Joachim of powershell24.de - It replaced my old crappy self developed approach.
.LINK
https://powershell24.de/en/2020/08/25/direkt-zugewiesene-plane-auslesen/
#>
[CmdletBinding(DefaultParameterSetName = 'Display',
ConfirmImpact = 'None')]
param
(
[Parameter(ParameterSetName = 'Export',
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[Alias('ExportInfo', 'ExportCSV')]
[switch]
$Export,
[Parameter(ParameterSetName = 'Display',
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[Alias('OutGridView', 'GridView', 'Info')]
[switch]
$Display,
[Parameter(ParameterSetName = 'Export',
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[Alias('CSVPath', 'CSVFile')]
[string]
$Path = '.\M365_admin_assignes_licenses.csv',
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[Alias('MsolShortName')]
[string]
$MsolName = 'contoso'
)
begin
{
#region RunVerbose
if ($PSCmdlet.MyInvocation.BoundParameters['Verbose'].IsPresent)
{
$RunVerbose = $true
}
else
{
$RunVerbose = $false
}
#endregion RunVerbose
#region DryRun
if ($PSCmdlet.MyInvocation.BoundParameters['Whatif'].IsPresent)
{
$IsDryRun = $true
}
else
{
$IsDryRun = $false
}
#endregion DryRun
#region GetUserInfo
$paramGetMsolUser = @{
All = $true
ErrorAction = 'Stop'
Verbose = $RunVerbose
}
$AllUsers = (Get-MsolUser @paramGetMsolUser)
# Filter the licensed users
$AllUsers = ($AllUsers | Where-Object {
$_.isLicensed -eq $true
})
#endregion GetUserInfo
#region CreateObjects
$paramNewObject = @{
TypeName = 'System.Collections.Generic.List[System.Object]'
Verbose = $RunVerbose
}
$DirectAssignments = (New-Object @paramNewObject)
$SkuFilter = (New-Object @paramNewObject)
$FilterSku = (New-Object @paramNewObject)
#endregion CreateObjects
#region LicenseFilters
[String[]]$FilterSku = @(
'POWER_BI_STANDARD'
'POWERAPPS_VIRAL'
'POWERAPPS_INDIVIDUAL_USER'
'FLOW_FREE'
'MCOMEETADV'
'PROJECTPROFESSIONAL'
)
#endregion LicenseFilters
#region FilterSku
foreach ($SkuFilterItem in $FilterSku)
{
$SkuFilterSingleItem = $null
$SkuFilterSingleItem = ($MsolName + ':' + $SkuFilterItem)
$SkuFilter.Add($SkuFilterSingleItem)
}
#endregion FilterSku
}
process
{
#region UserLoop
foreach ($User in $AllUsers)
{
# Be verbose
Write-Verbose -Message ('Gathering information for {0}' -f $User.UserPrincipalName)
# Store the object information
$UserObjectID = ($User.ObjectId)
$AllUserLicenses = ($User.Licenses)
#region LicenseLoop
foreach ($License in $AllUserLicenses)
{
# Store the object information
$Assignments = ($License.GroupsAssigningLicense)
#region IsLicenseAssigned
if ($License.GroupsAssigningLicense.Count -eq 0)
{
Write-Verbose -Message 'No direct assigned licenses found.'
}
else
{
# OK, now loop over the assigned licenses
foreach ($Assignment in $Assignments)
{
# Is it assigned to the user?
if ($Assignment -ieq $UserObjectID)
{
# Apply the Filter
if ($SkuFilter -match $License.AccountSkuId)
{
Write-Verbose -Message ('The License {0} was filtered.' -f $License.AccountSkuId)
}
else
{
# OK, we found something
$DirectAssignment = ('' | Select-Object -Property UserPrincipalName, AccountSkuId -Verbose:$RunVerbose)
$DirectAssignment.UserPrincipalName = ($User.UserPrincipalName)
$DirectAssignment.AccountSkuId = ($License.AccountSkuId.Replace(($MsolName + ':'), ''))
# Add to the list
$DirectAssignments.Add($DirectAssignment)
}
# Done
break
}
}
}
#endregion IsLicenseAssigned
}
#endregion LicenseLoop
}
#endregion UserLoop
}
end
{
#region SwitchHandler
switch ($PSCmdlet.ParameterSetName)
{
'Display'
{
# OK, dump the info
($DirectAssignments | Out-GridView -PassThru -Verbose:$RunVerbose)
}
'Export'
{
# export logic
$paramExportCsv = @{
Path = $Path
Force = $true
Encoding = 'UTF8'
NoTypeInformation = $true
Delimiter = ';'
Confirm = $false
Verbose = $RunVerbose
WhatIf = $IsDryRun
ErrorAction = 'Continue'
}
$null = ($DirectAssignments | Export-Csv @paramExportCsv)
}
}
#endregion SwitchHandler
}
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER

View File

@@ -0,0 +1,151 @@
#requires -Version 3.0 -Modules AzureAD, Microsoft.Online.SharePoint.PowerShell
<#
.SYNOPSIS
Provision new Users personal SharePoint site
.DESCRIPTION
Provision new Users personal SharePoint site, Will also trigger the OneDrive provisioning.
.PARAMETER TenantName
Microsoft 365 Tenant name (e.g. contoso for contoso.onmicrosoft.com)
Do not use any of the vanity domains of your tenant here!
.EXAMPLE
PS C:\> .\FixPersonalSPOSite.ps1
Provision new Users personal SharePoint site
.EXAMPLE
PS C:\> .\FixPersonalSPOSite.ps1 -TenantName 'contoso'
Provision new Users personal SharePoint site for the Microsoft 365 tenant with the name 'contoso' (for contoso.onmicrosoft.com)
.NOTES
I had issues where newly created users where unable to access there personal site/OneDrive via portal.office.com
We found this issue in at least two different tenants, therefore we decided to figure out a workaround.
Want to know how the magic workaround works?
See the last command of this script, Get-SPOSite does all the magic. Don't ask!
Author: Joerg Hochwald - https://hochwald.net
Contributor: Christopher Pope - https://hope-this-helps.de
#>
[CmdletBinding(ConfirmImpact = 'Low')]
param
(
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName,
Position = 0)]
[ValidateNotNullOrEmpty()]
[Alias('Tenant')]
[string]
$TenantName = ' contoso'
)
begin
{
# Set the URL main part
$AdminUrl = ('https://' + $TenantName + '-admin.sharepoint.com')
# Connect to AzureAD
Connect-AzureAD -ErrorAction Stop
# Connect to SharePoint Online
Connect-SPOService -Url $AdminUrl -ErrorAction Stop
}
process
{
# Get all User from the AzureAD
# Mind the Gap: -All is not a switch, it IS a Boolean <- WTF?
$paramGetAzureADUser = @{
All = $true
ErrorAction = 'SilentlyContinue'
}
$NewODFBUsers = (Get-AzureADUser @paramGetAzureADUser | Select-Object)
# Filter licensed users
$NewODFBUsers = ($NewODFBUsers | Where-Object -FilterScript {
<#
You can Filter much more, if you like
We Filter:
1. User with an assigned License
2. All external users (based on the '#EXT#' in the UserPrincipalName)
3. All users without a vanity domain (e.g. everyone within @NAME.onmicrosoft.com) <- Review this before using it!!!
#>
(($_.AssignedLicenses -ne $null) -and ($_.UserPrincipalName -notlike ('*#EXT#@*')) -and ($_.UserPrincipalName -notlike ('*@' + $TenantName + '.onmicrosoft.com')))
} | Select-Object -ExpandProperty UserPrincipalName -ErrorAction SilentlyContinue)
# The Limit of Request-SPOPersonalSite is 200
$SliceSize = 150
# Create a new Index
$SliceIndex = 0
# Slice the big array into smaller chunks
while ($($SliceSize * $SliceIndex) -lt $NewODFBUsers.Length)
{
# Cleanup
$NewODFBUsersSlice = $null
# Put the number of peaces into the new chunk (e.g. the new object)
$NewODFBUsersSlice = ($NewODFBUsers | Select-Object -First $SliceSize -Skip ($SliceSize * $SliceIndex) -ErrorAction SilentlyContinue)
# Just fire the Request, the hard limit is 200 per call
$paramRequestSPOPersonalSite = @{
UserEmails = $NewODFBUsersSlice
NoWait = $true
ErrorAction = 'SilentlyContinue'
WarningAction = 'SilentlyContinue'
}
$null = (Request-SPOPersonalSite @paramRequestSPOPersonalSite)
# Count the slice
$SliceIndex++
}
# Cool down and let Azure (SPO in this case) do the provisioning job in the background
Start-Sleep -Seconds 60
<#
Reference is Case #:23027858 (One Drive is not accessible via portal.office.com)
Solution: This get will do the magic! You have to do a Select on the "Owner" object to make the magic work.
Looks like the get will trigger something in the background!
#>
$paramGetSPOSite = @{
IncludePersonalSite = $true
Limit = 'all'
Filter = "Url -like '-my.sharepoint.com/personal/'"
ErrorAction = 'SilentlyContinue'
WarningAction = 'SilentlyContinue'
}
$null = (Get-SPOSite @paramGetSPOSite | Select-Object -Property Url, Owner)
}
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER

View File

@@ -0,0 +1,200 @@
function Get-MFAUserReport
{
<#
.SYNOPSIS
Get a Azure AD MFA User report
.DESCRIPTION
Get a Azure AD MFA User report, the function can export the report as CSV.
The export is disabled by default.
.PARAMETER Export
Export the MFA Report to CSV?
.PARAMETER Path
Path of the MFA Export CSV
.EXAMPLE
PS> Get-MFAUserReport
Get a Azure AD MFA User report
.EXAMPLE
PS> Get-MFAUserReport -Export
Get a Azure AD MFA User report and export it to the default report (C:\scripts\PowerShell\exports\MFAUsers.csv)
.EXAMPLE
PS> Get-MFAUserReport -Export -Path 'C:\scripts\PowerShell\exports\AllMFAUsers.csv'
Get a Azure AD MFA User report and export it to given report (C:\scripts\PowerShell\exports\AllMFAUsers.csv)
.NOTES
ParameterSet added
License: BSD 3-Clause
#>
[CmdletBinding(DefaultParameterSetName = 'Normal',
SupportsShouldProcess)]
param
(
[Parameter(ParameterSetName = 'Export',
ValueFromPipeline,
Position = 1)]
[Alias('CSV')]
[switch]
$Export,
[Parameter(ParameterSetName = 'Export',
ValueFromPipeline,
Position = 2)]
[string]
$Path = 'C:\scripts\PowerShell\exports\MFAUsers.csv'
)
begin
{
# Defaults
$CNT = 'Continue'
$STP = 'Stop'
# Cleanup
$Report = @()
$i = 0
if ($pscmdlet.ShouldProcess('MFA Users', 'Get'))
{
# get all Accounts
try
{
$Accounts = (Get-MsolUser -All -ErrorAction $STP -WarningAction $CNT | Where-Object -FilterScript {
$_.StrongAuthenticationMethods -ne $Null
} | Sort-Object -Property DisplayName)
}
catch
{
$line = ($_.InvocationInfo.ScriptLineNumber)
# Dump the Info
Write-Warning -Message ('Error was in Line {0}' -f $line)
# Dump the Error catched
Write-Error -Message $_ -ErrorAction $STP
# Something that should never be reached
break
}
}
}
process
{
if ($pscmdlet.ShouldProcess('MFA Users', 'Process'))
{
foreach ($Account in $Accounts)
{
$AccountDisplayName = $Account.DisplayName
Write-Verbose -Message ('Processing {0}' -f $AccountDisplayName)
# Counter
$i++
# Select Methods
$Methods = ($Account | Select-Object -ExpandProperty StrongAuthenticationMethods)
$MFA = ($Account | Select-Object -ExpandProperty StrongAuthenticationUserDetails)
$State = ($Account | Select-Object -ExpandProperty StrongAuthenticationRequirements)
$Methods | ForEach-Object -Process {
if ($_.IsDefault -eq $true)
{
$Method = $_.MethodType
}
}
if ($State.State)
{
$MFAStatus = $State.State
}
else
{
$MFAStatus = 'Disabled'
}
$Object = [PSCustomObject][Ordered]@{
User = $Account.DisplayName
UPN = $Account.UserPrincipalName
MFAMethod = $Method
MFAPhone = $MFA.PhoneNumber
MFAEmail = $MFA.Email
MFAStatus = $MFAStatus
}
# Add Obejct to report
$Report += $Object
}
}
}
end
{
if ($pscmdlet.ShouldProcess('MFA Users', 'Report'))
{
Write-Verbose -Message ('{0} accounts are MFA-enabled' -f $i)
if ($pscmdlet.ParameterSetName -eq 'Export')
{
try
{
$Null = ($Report | Export-Csv -NoTypeInformation -Path $Path -Force -ErrorAction $STP -WarningAction $CNT)
}
catch
{
$line = ($_.InvocationInfo.ScriptLineNumber)
# Dump the Info
Write-Warning -Message ('Error was in Line {0}' -f $line)
# Dump the Error catched
Write-Error -Message $_ -ErrorAction $STP
# Something that should never be reached
break
}
}
else
{
# Dump to console
$Report
}
}
}
}
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER

View File

@@ -0,0 +1,179 @@
function Get-MicrosoftCloudTenantInfo
{
<#
.SYNOPSIS
Check if a given Name is available as Office365/Azure Tenant Name
.DESCRIPTION
Check if a given Name is available as Office365/Azure Tenant Name and optional return the Tenant ID if the Tenant exists.
.PARAMETER name
Check if a given Name is available as Office365/Azure Tenant Name
.PARAMETER id
Get the Tenant ID
.EXAMPLE
PS C:\> Get-MicrosoftCloudTenantInfo -name 'Contoso' -id
The Tenant ID of contoso.onmicrosoft.com (contoso) is XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
.EXAMPLE
PS C:\> Get-MicrosoftCloudTenantInfo -name 'Contoso'
The Tenant contoso.onmicrosoft.com (contoso) is available
.EXAMPLE
PS C:\> Get-MicrosoftCloudTenantInfo -name 'Contoso'
WARNING: The Tenant contoso.onmicrosoft.com (contoso) is taken!
.NOTES
Changelog: Initial Public Release
#>
param
(
[Parameter(Mandatory,
ValueFromPipeline,
Position = 1,
HelpMessage = 'Check if a given Name is availible as Office365/Azure Tenant Name')]
[ValidateNotNullOrEmpty()]
[string]
$name,
[Alias('TenantID')]
[switch]
$id
)
begin
{
# Define some defaults
$ST = 'Stop'
$SC = 'SilentlyContinue'
# Do not use SSLv3 for any kind of Web Requests
if ([Net.ServicePointManager]::SecurityProtocol -notmatch 'TLS12')
{
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::TLS11
[Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::TLS12
}
# Cleanup
$available = $null
$TenantID = $null
$TenantLongName = $null
# Make the tenant name lowercase all the way, just in case!
$name = $name.ToLower()
# Where to check
$uri = 'https://portal.office.com/Signup/CheckDomainAvailability.ajax'
# OK, the Body looks creapy
$body = 'p0=' + $name + '&assembly=BOX.Admin.UI%2C+Version%3D16.0.0.0%2C+Culture%3Dneutral%2C+PublicKeyToken%3Dnull&class=Microsoft.Online.BOX.Signup.UI.SignupServerCalls'
}
process
{
# get the Info via Rest
$paramInvokeRestMethod = $null
$paramInvokeRestMethod = @{
Method = 'Post'
Uri = $uri
Body = $body
ErrorAction = $SC
WarningAction = $SC
}
$response = (Invoke-RestMethod @paramInvokeRestMethod)
# Error handler
$valid = $response.Contains('SessionValid')
if ($valid -eq $false)
{
# Whoops
Write-Error -Message $response -ErrorAction $ST
exit
}
# Looks good
$available = $response.Contains('<![CDATA[1]]>')
}
end
{
# Internal log Name
$TenantLongName = $name + '.onmicrosoft.com'
if ($available)
{
Write-Output -InputObject ('The Tenant {0} ({1}) is available' -f $TenantLongName, $name)
}
else
{
if ($id)
{
# Cleanup
$TenantID = $null
try
{
# Build the UIR
$TenantIDURI = 'https://login.windows.net/' + $name + '.onmicrosoft.com/.well-known/openid-configuration'
# Get the Info via regular call and split it
$paramInvokeWebRequest = $null
$paramInvokeWebRequest = @{
Uri = $TenantIDURI
ErrorAction = $ST
}
$TenantID = ((Invoke-WebRequest @paramInvokeWebRequest | ConvertFrom-Json -ErrorAction $ST).token_endpoint.Split('/')[3])
Write-Output -InputObject ('The Tenant ID of {0} ({1}) is {2}' -f $TenantLongName, $name, $TenantID)
}
catch
{
# Whoops
Write-Warning -Message 'The Tenant is taken, but we where unable to get the Tenant ID!!!'
}
}
else
{
Write-Warning -Message ('The Tenant {0} ({1}) is taken!' -f $TenantLongName, $name)
}
}
# Cleanup
$available = $null
$TenantID = $null
$TenantLongName = $null
}
}
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER

View File

@@ -0,0 +1,307 @@
#requires -Version 3.0 -Modules AzureAD, WhiteboardAdmin
function Get-MicrosoftWhiteboardReport
{
<#
.SYNOPSIS
Get all Whiteboards for a given user
.DESCRIPTION
Get all Whiteboards for a given UserID or UserPrincipalName
.PARAMETER UserId
The UserID (AzureAD Object ID) for the User
.PARAMETER UserName
The UserPrincipalName for the User
.EXAMPLE
PS C:\> Get-MicrosoftWhiteboardReport -UserId '43c67825-9835-48c8-9a85-6ecf681bf5c9'
Get all Whiteboards for the User with the Azure AD Object ID '43c67825-9835-48c8-9a85-6ecf681bf5c9'
.EXAMPLE
PS C:\> Get-MicrosoftWhiteboardReport -UserName 'john.doe@contoso.com'
Get all Whiteboards for the User 'john.doe@contoso.com'
.EXAMPLE
PS C:\> Get-MicrosoftWhiteboardReport -UserName 'john.doe@contoso.com' | Where-Object {$_.Id -eq '01b3d6ee-edbb-456a-8805-f768aaedcc6a'}
Get the infomation about the Whiteboard with the ID '01b3d6ee-edbb-456a-8805-f768aaedcc6a' ot hte user 'john.doe@contoso.com'
.OUTPUTS
psobject
.NOTES
Hard to automate: The WhiteboardAdmin does not have a connect function and will always prompt for auth (and then cache the credentials used to connect).
.LINK
Get-Whiteboard
.LINK
https://www.powershellgallery.com/packages/WhiteboardAdmin/
#>
[CmdletBinding(DefaultParameterSetName = 'UseID',
ConfirmImpact = 'None')]
[OutputType([psobject], ParameterSetName = 'UseID')]
[OutputType([psobject], ParameterSetName = 'UseName')]
[OutputType([psobject])]
param
(
[Parameter(ParameterSetName = 'UseID', HelpMessage = 'The UserID (AzureAD Obejct ID) for the User',
Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[Alias('ObejctID')]
[string]
$UserId,
[Parameter(ParameterSetName = 'UseName', HelpMessage = 'The UserPrincipalName for the User',
Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[Alias('UserPrincipalName')]
[string]
$UserName
)
begin
{
try
{
try
{
$null = (Get-AzureADTenantDetail -ErrorAction Stop)
}
catch
{
Connect-AzureAD
}
}
catch
{
#region ErrorHandler
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
$paramWriteError = @{
Message = $e.Exception.Message
ErrorAction = 'Stop'
Exception = $e.Exception
TargetObject = $e.CategoryInfo.TargetName
}
Write-Error @paramWriteError
# Only here to catch a global ErrorAction overwrite
exit 1
#endregion ErrorHandler
}
if ($PsCmdlet.ParameterSetName -eq 'UseName')
{
$UserId = (Get-AzureADUser -Filter ("userPrincipalName eq '{0}'" -f $UserName) | Select-Object -ExpandProperty ObjectId)
}
$UserWhiteboards = $null
$UserWhiteboards = (Get-Whiteboard -UserId $UserId -ErrorAction SilentlyContinue | Select-Object -Property *)
}
process
{
if ($UserWhiteboards)
{
# Create a new object for the report
$Report = @()
# Loop over the existing Whiteboards
foreach ($UserWhiteboard in $UserWhiteboards)
{
try
{
$objUserId = (Get-AzureADUser -ObjectId $UserWhiteboard.userId -ErrorAction SilentlyContinue | Select-Object -ExpandProperty DisplayName)
if (-not ($objUserId))
{
$objUserId = $UserWhiteboard.userId
}
$objCreatedBy = (Get-AzureADUser -ObjectId $UserWhiteboard.createdBy -ErrorAction SilentlyContinue | Select-Object -ExpandProperty DisplayName)
if (-not ($objCreatedBy))
{
$objCreatedBy = $UserWhiteboard.createdBy
}
$objOwnerId = (Get-AzureADUser -ObjectId $UserWhiteboard.ownerId -ErrorAction SilentlyContinue | Select-Object -ExpandProperty DisplayName)
if (-not ($objOwnerId))
{
$objOwnerId = $UserWhiteboard.ownerId
}
$ObjOwnerTenantId = (Get-AzureADTenantDetail)
if ($ObjOwnerTenantId.ObjectId -eq $UserWhiteboard.ownerTenantId)
{
$ObjOwnerTenantId = $ObjOwnerTenantId.DisplayName
}
else
{
$ObjOwnerTenantId = ('unknown (' + $UserWhiteboard.ownerTenantId + ')')
}
# Transform the DateTime String
$objCreated = ($UserWhiteboard.createdTime | Get-Date -Format 'yyyy-MM-dd HH:mm' -ErrorAction SilentlyContinue)
$objInvited = ($UserWhiteboard.invitedTime | Get-Date -Format 'yyyy-MM-dd HH:mm' -ErrorAction SilentlyContinue)
$objPersonalLastModified = ($UserWhiteboard.personalLastModifiedTime | Get-Date -Format 'yyyy-MM-dd HH:mm' -ErrorAction SilentlyContinue)
$objLastModified = ($UserWhiteboard.lastModifiedTime | Get-Date -Format 'yyyy-MM-dd HH:mm' -ErrorAction SilentlyContinue)
$objGlobalLastViewed = ($UserWhiteboard.globalLastViewedTime | Get-Date -Format 'yyyy-MM-dd HH:mm' -ErrorAction SilentlyContinue)
$objLastViewed = ($UserWhiteboard.lastViewedTime | Get-Date -Format 'yyyy-MM-dd HH:mm' -ErrorAction SilentlyContinue)
$obj = New-Object -TypeName psobject
$obj | Add-Member -MemberType NoteProperty -Name Title -Value $UserWhiteboard.title
$obj | Add-Member -MemberType NoteProperty -Name Id -Value $UserWhiteboard.id
$obj | Add-Member -MemberType NoteProperty -Name UserId -Value $objUserId
$objUserId = $null
$obj | Add-Member -MemberType NoteProperty -Name CreatedBy -Value $objCreatedBy
$objCreatedBy = $null
$obj | Add-Member -MemberType NoteProperty -Name OwnerId -Value $objOwnerId
$objOwnerId = $null
$obj | Add-Member -MemberType NoteProperty -Name OwnerTenant -Value $ObjOwnerTenantId
$ObjOwnerTenantId = $null
$obj | Add-Member -MemberType NoteProperty -Name IsShared -Value $UserWhiteboard.isShared
if ($objCreated)
{
$obj | Add-Member -MemberType NoteProperty -Name Created -Value $objCreated
$objCreated = $null
}
if ($objInvited)
{
$obj | Add-Member -MemberType NoteProperty -Name Invited -Value $objInvited
$objInvited = $null
}
if ($objPersonalLastModified)
{
$obj | Add-Member -MemberType NoteProperty -Name PersonalLastModified -Value $objPersonalLastModified
$objPersonalLastModified = $null
}
if ($objLastModified)
{
$obj | Add-Member -MemberType NoteProperty -Name LastModified -Value $objLastModified
$objLastModified = $null
}
if ($objGlobalLastViewed)
{
$obj | Add-Member -MemberType NoteProperty -Name GlobalLastViewed -Value $objGlobalLastViewed
$objGlobalLastViewed = $null
}
if ($objLastViewed)
{
$obj | Add-Member -MemberType NoteProperty -Name LastViewed -Value $objLastViewed
$objLastViewed = $null
}
if ($UserWhiteboard.meetingId)
{
$obj | Add-Member -MemberType NoteProperty -Name Meeting -Value $UserWhiteboard.meetingId
}
# Add to the report
$Report += $obj
# Cleanup
$obj = $null
}
catch
{
#region ErrorHandler
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $e.Exception.Message -WarningAction Continue -ErrorAction Continue
#endregion ErrorHandler
}
# Cleanup
$UserWhiteboards = $null
}
}
}
end
{
# Dump the report to the Terminal
if ($Report)
{
$Report
}
else
{
Write-Warning -Message 'There is not Whiteboard to report' -WarningAction Continue -ErrorAction Continue
}
}
}
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER

View File

@@ -0,0 +1,211 @@
function Get-O365MailboxServerDatacenterLocation
{
<#
.SYNOPSIS
Determines the datacenter and location of an Exchange Online Server by name
.DESCRIPTION
Determines the datacenter and location of an Exchange Online Server by name
Multiple Servers are supported.
The Following info is returned:
ServerName = The server name you used
Location = City (if known) or country
Region = Region (e.g. EUR for Europe, or NAM for North America)
GDPR = Is the Region EUR (Europe) or not
.PARAMETER ServerName
Server Name to check
.EXAMPLE
PS C:\> Get-O365MailboxServerDatacenterLocation -ServerName AM3PR1001MB1421
Sample Result:
ServerName Location Region GDPR
---------- -------- ------ ----
AM3PR1001MB1421 Amsterdam, Netherlands EUR True
The Server seems to be in Amsterdam, Netherlands (Europe)
.EXAMPLE
PS C:\> Get-O365MailboxServerDatacenterLocation -ServerName 'AM3PR1001MB1421 (15.20.3890.032)'
Sample Result:
ServerName Location Region GDPR
---------- -------- ------ ----
AM3PR1001MB1421 Amsterdam, Netherlands EUR True
The Server seems to be in Amsterdam, Netherlands (Europe)
The Server name needs to be the first word in the string, the rest is ignored!
.EXAMPLE
PS C:\> Get-O365MailboxServerDatacenterLocation -ServerName AZ3PR1001MB1421
Sample Result:
ServerName Location Region GDPR
---------- -------- ------ ----
AZ3PR1001MB1421 Unknown Unknown Unknown
This is the result if the server location is unknown
.EXAMPLE
PS C:\> Get-O365MailboxServerDatacenterLocation -ServerName 'AM3PR1001MB1421', 'AZ3PR1001MB1421'
Sample Result:
ServerName Location Region GDPR
---------- -------- ------ ----
AM3PR1001MB1421 Amsterdam, Netherlands EUR True
AZ3PR1001MB1421 Unknown Unknown Unknown
Query multiple servers at the same time
.NOTES
This is something I wrote for myself: I want to get detailed informations about Exchange servers that I find in some of the Microsoft Office 365 Logs
Limitation:
- Table of data-centers is static and may need to be expanded as Microsoft brings additional data-centers online
- If Microsoft decide to change the naming convention, the table of data-centers will become useless instantly
- The script works offline and does not check any plausibility (e.g. none existing servers, like in the examples above)
The Following info is returned:
ServerName = The server name you used
Location = City (if known) or country
Region = Region (e.g. EUR for Europe, or NAM for North America)
GDPR = Is the Region EUR (Europe) or not - This is one of the key functions for me!
Inspired by this blog post: https://adameyob.com/2018/03/30/exchange-online-woes
But my solution is slightly different: I use the server name I have from the logs to get the info
#>
[CmdletBinding(ConfirmImpact = 'None')]
param
(
[Parameter(Mandatory,
ValueFromPipelineByPropertyName,
Position = 0,
HelpMessage = 'Server Name')]
[Alias('M365ServerName', 'MailboxServer', 'O365MailboxServerName')]
[string[]]
$ServerName
)
begin
{
# Run Garbage Collection
[gc]::Collect()
# Create a new Hash-table
$Datacenter = (New-Object -TypeName Hashtable)
# Fill the Hash-table
$Datacenter['AM'] = @('EUR', 'Amsterdam, Netherlands')
$Datacenter['BL'] = @('NAM', 'Virginia, USA')
$Datacenter['BN'] = @('NAM', 'Virginia, USA')
$Datacenter['BY'] = @('NAM', 'San Francisco, California, USA')
$Datacenter['CH'] = @('NAM', 'Chicago, Illinois, USA')
$Datacenter['CO'] = @('NAM', 'Quincy, Washington, USA')
$Datacenter['CP'] = @('LAM', 'Brazil')
$Datacenter['CY'] = @('NAM', 'Cheyenne, Wyoming, USA')
$Datacenter['DB'] = @('EUR', 'Dublin, Ireland')
$Datacenter['DM'] = @('NAM', 'Des Moines, Iowa, USA')
$Datacenter['GR'] = @('LAM', 'Brazil')
$Datacenter['HE'] = @('EUR', 'Finland')
$Datacenter['HK'] = @('APC', 'Hong Kong')
$Datacenter['KA'] = @('JPN', 'Japan')
$Datacenter['KL'] = @('APC', 'Kuala Lumpur, Malaysia')
$Datacenter['LO'] = @('GBR', 'London, England')
$Datacenter['ME'] = @('APC', 'Melbourne, Victoria, Australia')
$Datacenter['MM'] = @('GBR', 'Durham, England')
$Datacenter['MW'] = @('NAM', 'Quincy, Washington, USA')
$Datacenter['OS'] = @('JPN', 'Japan')
$Datacenter['PS'] = @('APC', 'Busan, South Korea')
$Datacenter['SG'] = @('APC', 'Singapore')
$Datacenter['SI'] = @('APC', 'Singapore')
$Datacenter['SN'] = @('NAM', 'San Antonio, Texas, USA')
$Datacenter['SY'] = @('APC', 'Sydney, New South Wales, Australia')
$Datacenter['TY'] = @('JPN', 'Japan')
$Datacenter['VI'] = @('EUR', 'Austria')
$Datacenter['YQ'] = @('CAN', 'Quebec City, Canada')
$Datacenter['YT'] = @('CAN', 'Toronto, Canada')
}
process
{
$Result = (New-Object -TypeName System.Collections.Generic.List[System.Object])
foreach ($SingleServerName in $ServerName)
{
# Cleanup the Server name (Remove everything after the 1st word)
$SingleServerName = ($SingleServerName -split ' ')[0]
# This is a bit nasty, but unknown cause errors (null pointer)
try
{
$ObjectRegion = $Datacenter[$($SingleServerName.SubString(0, 2))][0]
}
catch
{
# If you know the info, please let me know!
$ObjectRegion = 'Unknown'
}
# This is a bit nasty, but unknown cause errors (null pointer)
try
{
$ObjectLocation = $Datacenter[$($SingleServerName.SubString(0, 2))][1]
}
catch
{
# If you know the info, please let me know!
$ObjectLocation = 'Unknown'
}
switch ($ObjectRegion)
{
'EUR'
{
$ObjectGDPR = $true
}
'Unknown'
{
# Unknown triggers an internal investigation (find the location Info ASAP)
$ObjectGDPR = 'Unknown'
}
default
{
# That triggers an Alarm in my SIEM
$ObjectGDPR = $false
}
}
# Keep the object in order!
$Object = [PSCustomObject][ordered]@{
ServerName = $SingleServerName
Location = $ObjectLocation
Region = $ObjectRegion
GDPR = $ObjectGDPR
}
# Add to the Output
$Result.Add($Object)
# Cleanup
$Object = $null
$ObjectLocation = $null
$ObjectRegion = $null
$ObjectGDPR = $null
}
}
end
{
# Dump to the Terminal
$Result
# Cleanup
$Result = $null
$Datacenter = $null
# Run Garbage Collection
[gc]::Collect()
}
}

View File

@@ -0,0 +1,724 @@
function Get-Office365Endpoints
{
<#
.SYNOPSIS
Get the Office 365 Endpoint Information from Microsoft via the new RestFull Webservice (JSON)
.DESCRIPTION
Microsoft updates the Office 365 IP address and FQDN entries at the end of each month and occasionally out of the cycle for operational or support requirements.
This function uses the new JSON based Webserice instead of the old XML based one; the XML based service will be retired soon by Microsoft.
The Function will compare the last downloaded version with the latest available online version, if there is no update available, the function does nothing.
If there is an update, the function will do what you told it to. If you want to enforce the download, just delete the O365_endpoints_*_latestversion.txt in your $Env:TEMP Directory. The * is a placeholder, for the Instance name.
.PARAMETER Instance
The short name of the Office 365 service instance.
Valid: Worldwide, USGovDoD, USGovGCCHigh, China, Germany
The default is: Worldwide
.PARAMETER Services
Valid items are All, Common, Exchange, SharePoint, Skype.
Because Common service area items are a prerequisite for all other service areas it is included every time - Adopted that from the Microsoft Statement; nevertheless, we disagree with the selection of Microsoft. There are way to many endpoints included here!
The default is: All
.PARAMETER Tenant
Your Office 365 tenant name.
The web service takes your provided name and inserts it in parts of URLs that include the tenant name.
If you don't provide a tenant name, those parts of URLs have the wildcard character (*).
.PARAMETER NoIPv6
Query string parameter. Set this to true to exclude IPv6 addresses from the output, for example, if you don't use IPv6 in your network.
The default is FALSE
.PARAMETER ExpressRoute
Only display endpoints that could be routed over ExpressRoute.
Default is: FALSE - All endpoints will be exported
.PARAMETER Category
The connectivity category for the endpoint set.
Valid values are: All, Optimize, Allow, and Default.
Default is: 'All'
.PARAMETER Required
This endpoint set is required to have connectivity for Office 365 to be supported.
Default is: FALSE
.PARAMETER Output
What to return?
Values are: All, IPv4, IPv6, URLs
Default is: All
.PARAMETER SkipVersionCheck
Force the download and ignore the existing version information
.EXAMPLE
PS C:\> Get-Office365Endpoints.ps1
It gets the International (Worldwide) Office 365 URLs, IPv4, and IPv6 address ranges.
.EXAMPLE
PS C:\> Get-Office365Endpoints.ps1 -Instance Germany
It gets the Office 365 Germany URLs, IPv4 address ranges. It would also return IPv6, but IPv6 is not supported, at least not yet.
.EXAMPLE
PS C:\> Get-Office365Endpoints.ps1 -Instance Germany -Category Optimize
It gets the Office 365 Germany URLs, IPv4 address ranges. Only in the category 'Optimize'. It would also return IPv6, but IPv6 is not supported, at least not yet.
.EXAMPLE
PS C:\> Get-Office365Endpoints.ps1 -Instance Worldwide -Services Exchange -Required
It gets the International (Worldwide) Office 365 URLs, IPv4, and IPv6 address ranges for Exchange and everything to be supported (includes CDNs and other, even external, services).
.EXAMPLE
PS C:\> Get-Office365Endpoints.ps1 -Instance Worldwide -Services Exchange -Required -Tenant 'contoso'
It gets the International (Worldwide) Office 365 URLs, IPv4, and IPv6 address ranges for Exchange and everything to be supported (includes CDNs and other, even external, services); this example includes URLs for the tenant with the Name 'contoso'.
The Tenant based URLs are generated and not checked, so please make sure you use the correct name!
.EXAMPLE
PS C:\> ((Get-Office365Endpoints.ps1 -Instance Worldwide -Services Exchange -Tenant 'contoso' -Output URLs -Required).url | Sort-Object -Unique) -join "," | Out-String
It gets the International (Worldwide) Office 365 URLs, IPv4, and IPv6 address ranges for Exchange and everything to be supported (includes CDNs and other, even external, services); this example includes URLs for the tenant with the Name 'contoso'.
The Tenant based URLs are generated and not checked, so please make sure you use the correct name! !
It just dumps the URLs in a comma separated (CSV) format. Useful for Proxy Servers.
.EXAMPLE
PS C:\> ((Get-Office365Endpoints.ps1 -Instance Worldwide -Services Exchange -Tenant 'contoso' -Output URLs -Required -SkipVersionCheck).url | Sort-Object -Unique) -join "," | Out-String
It gets the International (Worldwide) Office 365 URLs, IPv4, and IPv6 address ranges for Exchange and everything to be supported (includes CDNs and other, even external, services); this example includes URLs for the tenant with the Name 'contoso'.
The Tenant based URLs are generated and not checked, so please make sure you use the correct name! !
It just dumps the URLs in a comma separated (CSV) format. Useful for Proxy Servers.
The SkipVersionCheck Switch enforce the Download, without checking the local version.
.EXAMPLE
PS C:\> (((Get-Office365Endpoints.ps1 -Instance Worldwide -Services Exchange -Output IPv4) | Where-Object -FilterScript {$PSItem.tcpPorts -eq '587'}).ip | Sort-Object -Unique) -join "," | Out-String
It gets the International (Worldwide) Office 365 IPv4 addresses for Exchange Submission (SMTP) Servers who use Port 587. It dumps a comma separated (CSV) format. Useful for Firewalls.
.EXAMPLE
PS C:\> (((Get-Office365Endpoints.ps1 -Instance Worldwide -Services Exchange -Output IPv6) | Where-Object -FilterScript {$PSItem.tcpPorts -eq '25'}).ip | Sort-Object -Unique) -join "," | Out-String
It gets the International (Worldwide) Office 365 IPv4 addresses for Exchange SMTP Servers who use Port 25. It dumps a comma separated (CSV) format. Useful for Firewalls.
.EXAMPLE
PS C:\> (((Get-Office365Endpoints.ps1 -Instance Worldwide -Services Exchange -Output URLs) | Where-Object -FilterScript {$PSItem.notes -like '*Exchange Hybrid Configuration Wizard*' }).url | Sort-Object -Unique) -join "," | Out-String
Get a List of Exchange Online URLs that you might need if you want to run the Exchange Hybrid Configuration Wizard.
.EXAMPLE
PS C:\> ((Get-Office365Endpoints.ps1 -Instance Worldwide -Output 'IPv4' -ExpressRoute).ip | Sort-Object -Unique) -join "," | Out-String
Get a List of IPv4 addresses for ExpressRoute configuration.
.EXAMPLE
PS C:\> ((Get-Office365Endpoints.ps1 -Instance Worldwide -Output 'IPv6' -ExpressRoute).ip | Sort-Object -Unique) -join "," | Out-String
Get a List of IPv6 addresses for ExpressRoute configuration. Please note: IPv6 is not supported with ExpressRoute in every Instance, (example: Germany)
.EXAMPLE
PS C:\> ((Get-Office365Endpoints.ps1 -Instance Worldwide -NoIPv6).ip | Sort-Object -Unique) -join "," | Out-String
Get a list of IP addresses and exclude IPv6. The benefit of this parameter is the NoIPv6 parameter: The call will exclude the IPv6 Data from the response, and that might be smarter than filter it. It might be handy if you do NOT use IPv6 within your network - If this is the case, you might miss the future of networking! Think about that, before ignoring IPv6.
.EXAMPLE
$ExchangeOnlineSMTPEndpoints = (Get-Office365Endpoints.ps1 -Services Exchange) | Where-Object -FilterScript {
$PSItem.ip -and
$PSItem.DisplayName -eq 'Exchange Online' -and
$PSItem.tcpPorts -contains '25'
}
$ExchangeOnlineSMTPEndpoints.ip
Retrieve endpoints for Exchange Online and filter on TCP port 25
This is based on the following idea: http://www.powershell.no/exchange/online,office/365,powershell/2018/08/26/automate-office365-ip-address-handling.html
.NOTES
Function that uses the new Microsoft Service. A few things are still missing or not rock solid.
However, we needed a solution to configure ExpressRoute now, so we started with some rework to use the new web service.
This function is part of the commercial en.Office365 PowerShell Module - Distributed separately as OpenSource with a very flexible license (See below)
Some parts of the script are based upon the example that Microsoft published on the info page of the new web service!
.LINK
https://github.com/jhochwald/PowerShell-collection/blob/master/Office365/Get-Office365Endpoints.ps1
.LINK
https://hochwald.net/powershell-get-the-office-365-endpoint-information-from-microsoft/
.LINK
https://hochwald.net/powershell-function-to-get-the-office-365-urls-and-ip-address-ranges/
.LINK
https://support.office.com/en-us/article/managing-office-365-endpoints-99cab9d4-ef59-4207-9f2b-3728eb46bf9a#webservice
.LINK
https://techcommunity.microsoft.com/t5/Office-365-Blog/Announcing-Office-365-endpoint-categories-and-Office-365-IP/ba-p/177638
#>
[CmdletBinding(ConfirmImpact = 'None')]
[OutputType([psobject])]
param
(
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[ValidateSet('Worldwide', 'USGovDoD', 'USGovGCCHigh', 'China', 'Germany', IgnoreCase = $true)]
[ValidateNotNullOrEmpty()]
[string]
$Instance = 'Worldwide',
[Parameter(ValueFromPipeline)]
[ValidateSet('All', 'Common', 'Exchange', 'SharePoint', 'Skype', IgnoreCase = $true)]
[ValidateNotNullOrEmpty()]
[Alias('ServiceAreas')]
[string]
$Services = 'All',
[Parameter(ValueFromPipeline)]
[Alias('TenantName')]
[string]
$Tenant = $null,
[Parameter(ValueFromPipeline)]
[switch]
$NoIPv6,
[Parameter(ValueFromPipeline)]
[switch]
$ExpressRoute,
[ValidateSet('All', 'Optimize', 'Allow', 'Default', IgnoreCase = $true)]
[string[]]
$Category,
[Parameter(ValueFromPipeline)]
[switch]
$Required,
[Parameter(ValueFromPipeline)]
[ValidateSet('All', 'IPv4', 'IPv6', 'URLs', IgnoreCase = $true)]
[string]
$Output = 'All',
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[Alias('ForceDownload')]
[switch]
$SkipVersionCheck = $false
)
begin
{
#region MakeIPv6Plausible
if (($NoIPv6) -and ($Output -eq 'IPv6'))
{
# This makes no sense, and we totally ignore to do it!
Write-Error -Message 'The selected parameters make no sense; we cannot continue!' -ErrorAction Stop
# We should never reach this point!
break
}
#endregion MakeIPv6Plausible
#region CategoryTweaker
if ((! $Category) -or ($Category -eq 'All'))
{
Write-Verbose -Message 'We get all categories.'
# Set to all
$Category += 'Optimize', 'Allow', 'Default'
}
#endregion CategoryTweaker
#region TweakOutputHandler
<#
TODO: Make a simpler solution for that!
#>
switch ($Output)
{
'All'
{
Write-Verbose -Message 'Dump all Infos (IPv4, IPv6, and URLs)'
$outIPv4 = $true
$outIPv6 = $true
$outURLs = $true
}
'IPv4'
{
Write-Verbose -Message 'Dump IPv4 Infos'
$outIPv4 = $true
$outIPv6 = $false
$outURLs = $false
}
'IPv6'
{
Write-Verbose -Message 'Dump IPv6 Infos'
$outIPv4 = $false
$outIPv6 = $true
$outURLs = $false
}
'URLs'
{
Write-Verbose -Message 'Dump URLs Infos'
$outIPv4 = $false
$outIPv6 = $false
$outURLs = $true
}
}
#endregion TweakOutputHandler
#region ConfigurationVariables
# Web service root URL
$BaseURI = 'https://endpoints.office.com'
Write-Verbose -Message ('We use {0} as Base URL' -f $BaseURI)
# Path where client ID and latest version number will be stored
<#
TODO: Move the Location to a parameter
#>
$datapath = $Env:TEMP + '\O365_endpoints_' + $Instance + '_latestversion.txt'
Write-Verbose -Message ('We save the Endpoint Version Information to {0}' -f $datapath)
#endregion ConfigurationVariables
#region LocalVersionChecker
# fetch client ID and version if data file exists; otherwise create new file
if (Test-Path -Path $datapath)
{
Write-Verbose -Message 'We get the information from Microsoft...'
# Read the File
$content = (Get-Content -Path $datapath)
# Get the Info
$clientRequestId = $content[0]
$lastVersion = $content[1]
# Cleanup
$content = $null
}
else
{
Write-Verbose -Message 'Old version information file exists, start to gather the Info!'
# Create a GUID
$clientRequestId = [GUID]::NewGuid().Guid
# Dummy Data
$lastVersion = '0000000000'
# Save the local info
try
{
@($clientRequestId, $lastVersion) | Out-File -FilePath $datapath -ErrorAction Stop
}
catch
{
# Write the complete error if we have verbose turned on
Write-Verbose -Message $_
# Our Error test
Write-Error -Message ('Unable to write Datafile: {0}' -f $datapath) -ErrorAction Stop
# We should never reach this point!
break
}
}
#endregion LocalVersionChecker
#region RemoteVersionChecker
# Call version method to check the latest version, and pull new data if version number is different
try
{
# Splat the parameters
$GetVersionParams = @{
Uri = ($BaseURI + '/version/' + $Instance + '?clientRequestId=' + $clientRequestId)
Method = 'Get'
ErrorAction = 'Stop'
WarningAction = 'SilentlyContinue'
}
Write-Verbose -Message ('We use {0} as request URI.' -f ($GetVersionParams.Uri))
$version = (Invoke-RestMethod @GetVersionParams)
}
catch
{
# Write the complete error if we have verbose turned on
Write-Verbose -Message $_
# Our Error test
Write-Error -Message 'Unable to get the new Office 365 Endpoint Information' -ErrorAction Stop
# We should never reach this point!
break
}
#endregion RemoteVersionChecker
}
process
{
#region VersionCompare
if (($SkipVersionCheck -eq $true) -or ($version.latest -gt $lastVersion))
{
Write-Verbose -Message ('New version of Office 365 {0} endpoints detected' -f $Instance)
# Write the new version number to the data file
try
{
@($clientRequestId, $version.latest) | Out-File -FilePath $datapath -ErrorAction Stop
}
catch
{
# Write the complete error if we have verbose turned on
Write-Verbose -Message $_
# Our Error test
Write-Error -Message ('Unable to write Datafile: {0}' -f $datapath) -ErrorAction Stop
# We should never reach this point!
break
}
#endregion VersionCompare
#region GetTheEndpoints
try
{
# Set the default URI
$requestURI = ($BaseURI + '/endpoints/' + $Instance + '?clientRequestId=' + $clientRequestId)
switch ($Services)
{
'All'
{
# We get all
}
'Common'
{
# Append to the URI
$requestURI = ($requestURI + '&ServiceAreas=Common')
}
'Exchange'
{
# Append to the URI
$requestURI = ($requestURI + '&ServiceAreas=Exchange')
}
'SharePoint'
{
# Append to the URI
$requestURI = ($requestURI + '&ServiceAreas=SharePoint')
}
'Skype'
{
# Append to the URI
$requestURI = ($requestURI + '&ServiceAreas=Skype')
}
}
if ($Tenant)
{
# Append to the URI - Build URL for the Tenant
$requestURI = ($requestURI + '&TenantName=' + $Tenant)
}
if ($NoIPv6)
{
# Append to the URI - Exclude IPv6 addresses from the output
$requestURI = ($requestURI + '&NoIPv6')
Write-Verbose -Message 'IPv6 addresses are excluded from the output! IPv6 is the future, think about an adoption soon.'
}
# Do our job and get the data via Rest Request
Write-Verbose -Message ('We request the following URI: {0}' -f $requestURI)
$endpointSetsParams = @{
Uri = $requestURI
Method = 'Get'
ErrorAction = 'Stop'
WarningAction = 'SilentlyContinue'
}
$endpointSets = (Invoke-RestMethod @endpointSetsParams)
}
catch
{
# Write the complete error if we have verbose turned on
Write-Verbose -Message $_
# Our Error test
Write-Error -Message 'Unable to get the new Office 365 Endpoint Information' -ErrorAction Stop
# We should never reach this point!
break
}
#endregion GetTheEndpoints
#region FilterURLs
if ($outURLs)
{
$flatUrls = $endpointSets | ForEach-Object -Process {
$endpointSet = $PSItem
$urls = $(if ($endpointSet.urls.Count -gt 0)
{
$endpointSet.urls
}
else
{
@()
}
)
# Cleanup
$urlCustomObjects = @()
if ($endpointSet.category -in ($Category))
{
$urlCustomObjects = $urls | ForEach-Object -Process {
# Ordered is slower, but we like it this way
[PSCustomObject][ordered]@{
id = $endpointSet.id
serviceArea = $endpointSet.serviceArea
DisplayName = $endpointSet.serviceAreaDisplayName
url = $PSItem
tcpPorts = $endpointSet.tcpPorts
udpPorts = $endpointSet.udpPorts
expressRoute = $endpointSet.expressRoute
category = $endpointSet.category
required = $endpointSet.required
notes = $endpointSet.notes
}
}
}
# Only ExpressRoute enabled Objects?
if ($ExpressRoute)
{
$urlCustomObjects = $urlCustomObjects | Where-Object -FilterScript {
$urlCustomObjects.expressRoute -eq $true
}
}
# Only required to have connectivity for Office 365 to be supported
if ($Required)
{
$urlCustomObjects = $urlCustomObjects | Where-Object -FilterScript {
$urlCustomObjects.required -eq $true
}
}
# Dump
$urlCustomObjects
}
}
#endregion FilterURLs
#region FilterIPv4
if ($outIPv4)
{
$flatIpv4 = $endpointSets | ForEach-Object -Process {
$endpointSet = $PSItem
$ips = $(if ($endpointSet.ips.Count -gt 0)
{
$endpointSet.ips
}
else
{
@()
}
)
# IPv4 strings have dots while IPv6 strings have colons
$IPv4 = $ips | Where-Object -FilterScript {
$PSItem -like '*.*'
}
# Cleanup
$ipCustomObjects = @()
if ($endpointSet.category -in ($Category))
{
$ipCustomObjects = $IPv4 | ForEach-Object -Process {
# Ordered is slower, but we like it this way
[PSCustomObject][ordered]@{
id = $endpointSet.id
serviceArea = $endpointSet.serviceArea
DisplayName = $endpointSet.serviceAreaDisplayName
ip = $PSItem
tcpPorts = $endpointSet.tcpPorts
udpPorts = $endpointSet.udpPorts
expressRoute = $endpointSet.expressRoute
category = $endpointSet.category
required = $endpointSet.required
notes = $endpointSet.notes
}
}
}
# Dump
$ipCustomObjects
}
}
#endregion FilterIPv4
#region FilterIPv6
if ($outIPv6)
{
$flatIpv6 = $endpointSets | ForEach-Object -Process {
$endpointSet = $PSItem
$ips = $(if ($endpointSet.ips.Count -gt 0)
{
$endpointSet.ips
}
else
{
@()
}
)
# IPv4 strings have dots while IPv6 strings have colons
$IPv6 = $ips | Where-Object -FilterScript {
$PSItem -like '*:*'
}
# Cleanup
$ipCustomObjects = @()
if ($endpointSet.category -in ($Category))
{
$ipCustomObjects = $IPv6 | ForEach-Object -Process {
# Ordered is slower, but we like it this way
[PSCustomObject][ordered]@{
id = $endpointSet.id
serviceArea = $endpointSet.serviceArea
DisplayName = $endpointSet.serviceAreaDisplayName
ip = $PSItem
tcpPorts = $endpointSet.tcpPorts
udpPorts = $endpointSet.udpPorts
expressRoute = $endpointSet.expressRoute
category = $endpointSet.category
required = $endpointSet.required
notes = $endpointSet.notes
}
}
}
# Dump
$ipCustomObjects
}
}
#endregion FilterIPv4
}
}
end
{
if (($SkipVersionCheck -eq $true) -or ($version.latest -gt $lastVersion))
{
#region DumpIPv4
if ($outIPv4)
{
Write-Verbose -Message 'Office 365 IPv4 IP Address Ranges'
($flatIpv4 | Sort-Object -Property id)
}
#endregion DumpIPv4
#region DumpIPv6
if ($outIPv6)
{
Write-Verbose -Message 'Office 365 IPv6 IP Address Ranges'
($flatIpv6 | Sort-Object -Property id)
}
#endregion DumpIPv6
#region DumpURLs
if ($outURLs)
{
Write-Verbose -Message 'Office 365 URLs'
($flatUrls | Sort-Object -Property id)
}
#endregion DumpURLs
}
else
{
#region DumpInfoNothing
<#
This 'else' loop is here as a placeholder in this script!
We use this in the commercial version (function within the commercial module)
#>
Write-Output -InputObject ('The {0} Office 365 endpoints are up-to-date' -f $Instance)
#endregion DumpInfoNothing
}
}
#region CHANGELOG
<#
CHANGELOG:
0.8.6 - 2019.01-04:
[CHANGE] Converted back to a function to make it easier for me (no more need to adopt between my sources)
[ADD] Start to add regions
0.8.5 - 2018-10-04:
[FIX] Fix the Output to reflect the correct Instance name (PSMO365-48)
[ADD] Add -SkipVersionCheck Switch to force the download. As request by @mikes-gh in #4 in GitHub (PSMO365-49)
[FIX] Fix a view typos and errors
0.8.4 - 2018-08-29:
[ADD] Exchange Online Example added (Source http://www.powershell.no/exchange/online,office/365,powershell/2018/08/26/automate-office365-ip-address-handling.html)
[CHANGE] Tweaks (after internal code review and refactoring)
0.8.3 - 2018-08-20 - Unreleased:
[ADD] We added a few more verbose outputs. Verbose Implementation us based upon request. (PSMO365-43)
[CHANGE] Region name change
0.8.2 - 2018-08-19:
[ADD] Regions added to make the code more readable within code editors (PSMO365-47)
[FIX] A few typos in the descriptions where fixed - No change to any code or logic
0.8.1 - 2018-08-19:
[FIX] Add missing OutputType (PSMO365-41)
[CHANGE] datafile name tweaked (PSMO365-42)
[ADD] Missing NoIPv6 switch function implemented (PSMO365-44)
[ADD] New Example for NoIPv6 switch (PSMO365-45)
[ADD] A few more links
[ADD] Info about the datafile (PSMO365-42)
[ADD] Embed a few things as comment - Due to the separation from the Module
[ADD] This changelog within the code - Reflect the changes within the dedicated function (PSMO365-46)
0.8.0 - 2018-08-18:
[INIT] Initial public release
#>
#endregion CHANGELOG
}
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER

View File

@@ -0,0 +1,248 @@
#requires -Version 3.0 -Modules Microsoft.Online.SharePoint.PowerShell
<#
.SYNOPSIS
Generates a basic usage report for OneDrive for Business sites
.DESCRIPTION
Generates a basic usage report for OneDrive for Business sites
The report will contain the following information:
- Owner (UPN)
- CurrentUsage (GB)
- Quota (GB)
- QuotaWarning (GB)
- QuotaType
- LastModified
- Status
.PARAMETER TenantName
The Tenant name, like contoso if the tenant is contoso.onmicrosoft.com
vanity names, e.g. contoso.com, are NOT supported!
.NOTES
Quick and dirty implementation to generate a simple CSV report file
#>
[CmdletBinding(ConfirmImpact = 'None')]
param
(
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[ValidateNotNull()]
[Alias('Tenant', 'M365Name', 'M365TenantName')]
[string]
$TenantName = $null
)
begin
{
# Garbage Collection
[GC]::Collect()
try
{
$paramImportModule = @{
Name = 'Microsoft.Online.SharePoint.PowerShell'
DisableNameChecking = $true
NoClobber = $true
Force = $true
ErrorAction = 'SilentlyContinue'
WarningAction = 'Stop'
}
$null = (Import-Module @paramImportModule)
}
catch
{
#region ErrorHandler
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
$paramWriteError = @{
Message = $e.Exception.Message
ErrorAction = 'Stop'
Exception = $e.Exception
TargetObject = $e.CategoryInfo.TargetName
}
Write-Error @paramWriteError
# Only here to catch a global ErrorAction overwrite
exit 1
#endregion ErrorHandler
}
# Create the Connection URI
$AdminURL = ('https://' + $TenantName + '-admin.sharepoint.com')
# Connect to SharePoint Online
$paramConnectSPOService = @{
Url = $AdminURL
Region = 'Default'
ErrorAction = 'Stop'
}
$null = (Connect-SPOService @paramConnectSPOService)
# Create new object
$Report = @()
}
process
{
$paramGetSPOSite = @{
IncludePersonalSite = $true
Limit = 'all'
Filter = "Url -like '-my.sharepoint.com/personal/'"
ErrorAction = 'SilentlyContinue'
}
$Users = (Get-SPOSite @paramGetSPOSite | Select-Object -ExpandProperty Url)
foreach ($User in $Users)
{
try
{
# Cleanup
$Stats = $null
$StatsReport = $null
# Get the dedicated Info for the user
$paramGetSPOSite = @{
Identity = $User
ErrorAction = 'Stop'
}
$Stats = (Get-SPOSite @paramGetSPOSite | Select-Object -Property LastContentModifiedDate, Owner, StorageUsageCurrent, StorageQuota, StorageQuotaWarningLevel, StorageQuotaType, Status)
# Create the Reporting object
$StatsReport = [PSCustomObject]@{
Owner = $Stats.Owner
CurrentUsage = '{0:F3}' -f ($Stats.StorageUsageCurrent / 1024) -as [decimal]
Quota = '{0:F0}' -f ($Stats.StorageQuota / 1024) -as [int]
QuotaWarning = '{0:F0}' -f ($Stats.StorageQuotaWarningLevel / 1024) -as [int]
QuotaType = $Stats.StorageQuotaType
LastModified = $Stats.LastContentModifiedDate
Status = $Stats.Status
}
# Append the report
$Report += $StatsReport
}
catch
{
#region ErrorHandler
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception
#endregion ErrorHandler
}
}
}
end
{
# Create a Timestamp (check if this is OK for you)
$TimeStamp = (Get-Date -Format yyyyMMdd_HHmmss)
# Export the CSV Report
try
{
$paramExportCsv = @{
Path = ('.\OneDriveUsageReport' + $TimeStamp + '.csv')
Force = $true
Encoding = 'UTF8'
Delimiter = ';'
NoTypeInformation = $true
ErrorAction = 'Stop'
}
($Report | Sort-Object -Property CurrentUsage -Descending | Export-Csv @paramExportCsv)
}
catch
{
#region ErrorHandler
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
$paramWriteError = @{
Message = $e.Exception.Message
ErrorAction = 'Stop'
Exception = $e.Exception
TargetObject = $e.CategoryInfo.TargetName
}
Write-Error @paramWriteError
#endregion ErrorHandler
}
finally
{
# Cleanup
$Report = $null
# Disconnect from SharePoint Online
$null = (Disconnect-SPOService -ErrorAction SilentlyContinue)
# Garbage Collection
[GC]::Collect()
}
}
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER

View File

@@ -0,0 +1,803 @@
function Initialize-ActivityAlertSet
{
<#
.SYNOPSIS
Create good practice Ruleset of Office 365 Activity Alert's
.DESCRIPTION
Create good practice Ruleset of Office 365 Activity Alert's
You need a PowerShell connection to the Security and Compliance Center
.PARAMETER NotifyUser
The NotifyUser parameter specifies the email addresses for notification messages.
You can specify internal and external email addresses (even mix them).
You can specify multiple email addresses separated by commas.
.PARAMETER UserId
The UserId parameter specifies who you want to monitor.
If you specify a user's email address, you'll receive an email notification when the user performs the specified activity.
You can specify multiple email addresses separated by commas.
If this parameter is blank ($null), you'll receive an email notification when any user in your organization performs the specified activity.
Default is $null (Activity alert is triggered for any user)
.PARAMETER EmailCulture
The EmailCulture parameter specifies the language of the notification email message.
Valid input for this parameter is a supported culture code value from the Microsoft .NET Framework CultureInfo class.
For example, de-DE for German, da-DK for Danish or ja-JP for Japanese.
The default is en-US
.EXAMPLE
PS C:\> Initialize-ActivityAlertSet -NotifyUser 'alert@contoso.com'
Create good practice Ruleset of Office 365 Activity Alert's and send all alters to 'alert@contoso.com'
.EXAMPLE
PS C:\> Initialize-ActivityAlertSet -NotifyUser 'alert@contoso.com -UserId 'john.doe@contoso.com'
Create good practice Ruleset of Office 365 Activity Alert's and send all alters to 'alert@contoso.com',
only monitor the user 'john.doe@contoso.com'
.EXAMPLE
PS C:\> Initialize-ActivityAlertSet -NotifyUser 'alert@contoso.com -UserId 'john.doe@contoso.com' -EmailCulture 'de-DE'
Create good practice Ruleset of Office 365 Activity Alert's and send all alters to 'alert@contoso.com',
only monitor the user 'john.doe@contoso.com' and send all alters in German!
.EXAMPLE
PS C:\> Initialize-ActivityAlertSet -NotifyUser 'alert@contoso.com -UserId 'john.doe@contoso.com', 'jane.doe@contoso.com'
Create good practice Ruleset of Office 365 Activity Alert's and send all alters to 'alert@contoso.com',
only monitor the users 'john.doe@contoso.com' and 'jane.doe@contoso.com'
.NOTES
Please Review all the settings carefully before your run the script!
You must have a connection to the following Office 365 services:
- Security and Compliance Center
All features should work with your default Office 365 Enterprise plan. Business plans are not tested!
PLEASE NOTE:
This is really just a basic setup. It does NOT replace an security advice by a security consultant!
.LINK
New-ActivityAlert
#>
[CmdletBinding(ConfirmImpact = 'Low')]
param
(
[Parameter(Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName,
HelpMessage = 'The NotifyUser parameter specifies the email addressesfor notification messages.')]
[ValidateNotNullOrEmpty()]
[Alias('AlertMail')]
[string[]]
$NotifyUser,
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[AllowEmptyCollection()]
[AllowEmptyString()]
[AllowNull()]
[string[]]
$UserId = $null,
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[cultureinfo]
$EmailCulture = 'en-US'
)
begin
{
if (-not (Get-Command -Name New-ProtectionAlert))
{
Write-Error -Exception 'Not connected to the Office 365 Security & Compliance Center' -Message 'Use ''Connect-IPPSSession'' to connect to the Office 365 Security & Compliance Center' -Category OperationStopped -RecommendedAction 'Use ''Connect-IPPSSession'' to connect to the Office 365 Security & Compliance Center' -ErrorAction Stop
exit 1
}
Write-Output -InputObject "Create good practice Ruleset of Office 365 Activity Alert's"
}
process
{
#region
try
{
$paramNewActivityAlert = @{
Name = 'File and Page Alert'
Operation = 'Filemalwaredetected'
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = 'SharePoint anti-virus engine detects malware in a file.'
Severity = 'Low'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
Category = 'ThreatManagement'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewActivityAlert = @{
Name = 'Anonymous Links Alert'
Operation = 'Anonymouslinkcreated', 'Anonymouslinkupdated'
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = "An anonymous link (also called an 'Anyone' link) was created/updated for a resource."
Severity = 'High'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
Category = 'DataLossPrevention'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewActivityAlert = @{
Name = 'Anonymous Links Access Alert'
Operation = 'Anonymouslinkused'
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = "An anonymous link (also called an 'Anyone' link) was used for a resource."
Severity = 'High'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
Category = 'DataLossPrevention'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewActivityAlert = @{
Name = 'Sharing Alert'
Operation = 'Sharinginvitationcreated', 'Sharingpolicychanged'
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = "User shared a resource in SharePoint Online or OneDrive for Business with a user who isn't in your organization's directory. A SharePoint or global administrator changed a SharePoint sharing policy."
Severity = 'Low'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
Category = 'DataLossPrevention'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewActivityAlert = @{
Name = 'Access Alert'
Operation = 'Deviceaccesspolicychanged', 'Networkaccesspolicychanged'
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = 'Change in the unmanaged devices policy. Change in the location-based access policy (also called a trusted network boundary).'
Severity = 'Low'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
}
#endregion
#region
try
{
$paramNewActivityAlert = @{
Name = 'Site Alert'
Operation = 'Sitecollectioncreated', 'Sitedeleted', 'Sitecollectionadminadded'
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = 'Creation of a new site collection OneDrive for Business site provisioned. A site was deleted.Site collection administrator or owner adds a person as a site collection administrator for a site.'
Severity = 'Low'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
Category = 'DataGovernance'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewActivityAlert = @{
Name = 'Office Alert'
Operation = 'Officeondemandset'
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = 'Site administrator enables Office on Demand, which lets users access the latest version of Office desktop applications.'
Severity = 'Low'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
Category = 'Others'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewActivityAlert = @{
Name = 'Mailbox Alert'
Operation = 'Add-MailboxPermission', 'Remove-MailboxPermission'
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = "An administrator assigned/removed the FullAccess mailbox permission to a user (known as a delegate) to another person`'s mailbox"
Severity = 'Medium'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
Category = 'AccessGovernance'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewActivityAlert = @{
Name = 'Password Alert'
Operation = 'Change user password.', 'Reset user password.', 'Set force change user password.'
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = 'User password changes'
Severity = 'Medium'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
Category = 'ThreatManagement'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewActivityAlert = @{
Name = 'Role Alert'
Operation = 'Add member to role.', 'Remove member from role.'
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = 'Added/Removed a user to an admin role in Office 365.'
Severity = 'Medium'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
Category = 'AccessGovernance'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewActivityAlert = @{
Name = 'Company Information Alert'
Operation = 'Set company contact information.', 'Set company information.', 'Set password policy.', 'Remove partner from company.'
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = 'Change company information or password policy'
Severity = 'Medium'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
Category = 'DataGovernance'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewActivityAlert = @{
Name = 'Domain Alert'
Operation = 'Add domain to company.', 'Update domain.'
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = 'Change of a custom domain in a tenant'
Severity = 'Low'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
Category = 'DataGovernance'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewActivityAlert = @{
Name = 'Domain Remove Alert'
Operation = 'Remove domain from company.'
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = 'Remove of a custom domain in a tenant'
Severity = 'Medium'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
Category = 'DataGovernance'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewActivityAlert = @{
Name = 'First and Second Stage Recycle Bin'
Operation = @('filedeletedfirststagerecyclebin', 'filedeletedsecondstagerecyclebin', 'folderdeletedfirststagerecyclebin', 'folderdeletedsecondstagerecyclebin')
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = 'Notify when items are deleted from first or second stage recycle bin'
Severity = 'High'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
Category = 'DataLossPrevention'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewActivityAlert = @{
Name = 'Transport Rules Monitoring Alerts'
Operation = 'New-TransportRule', 'Set-TransportRule', 'Remove-TransportRule'
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = 'Creation, Modification and Deletion of Transport Rules'
Severity = 'High'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
RecordType = 'ExchangeAdmin'
Category = 'ThreatManagement'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
# MCAS might be the better option, but this requires proper licensing to fully use all of its functionality
$paramNewActivityAlert = @{
Name = 'Sharepoint Folder or File is shared with an external party'
Operation = 'securelinkcreated'
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = "A user has created a 'specific people link' to share a resource with a specific person. This target user may be someone who's external to your organization"
Severity = 'Medium'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
Category = 'DataLossPrevention'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
# MCAS might be the better option, but this requires proper licensing to fully use all of its functionality
$paramNewActivityAlert = @{
Name = 'Sharepoint Folder or File is shared company-wide'
Operation = 'CompanylinkCreated'
NotifyUser = $NotifyUser
UserId = $UserId
EmailCulture = $EmailCulture
Description = "User created a company-wide link to a resource. company-wide links can only be used by members in your organization. They can't be used by guests."
Severity = 'Low'
Type = 'Custom'
ErrorAction = 'Stop'
WarningAction = 'Continue'
Category = 'DataGovernance'
}
$null = (New-ActivityAlert @paramNewActivityAlert)
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
}
end
{
Write-Output "Check the new Activity Alert's in your Office 365 Security & Compliance Center"
}
}
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER

View File

@@ -0,0 +1,474 @@
function Initialize-ProtectionAlertSet
{
<#
.SYNOPSIS
Create good practice Ruleset of Office 365 Protection Alert's
.DESCRIPTION
Create good practice Ruleset of Office 365 Protection Alert's
You need a PowerShell connection to the Security and Compliance Center
.PARAMETER NotifyUser
The NotifyUser parameter specifies the email addresses for notification messages.
You can specify internal and external email addresses (even mix them).
You can specify multiple email addresses separated by commas.
.PARAMETER UserId
The UserId parameter specifies who you want to monitor.
If you specify a user's email address, you'll receive an email notification when the user performs the specified activity.
You can specify multiple email addresses separated by commas.
If this parameter is blank ($null), you'll receive an email notification when any user in your organization performs the specified activity.
Default is $null (Activity alert is triggered for any user)
.PARAMETER EmailCulture
The EmailCulture parameter specifies the language of the notification email message.
Valid input for this parameter is a supported culture code value from the Microsoft .NET Framework CultureInfo class.
For example, de-DE for German, da-DK for Danish or ja-JP for Japanese.
The default is en-US
.EXAMPLE
PS C:\> Initialize-ProtectionAlertSet -NotifyUser 'alert@contoso.com'
Create good practice Ruleset of Office 365 Protection Alert's and send all alerts to 'alert@contoso.com'
.EXAMPLE
PS C:\> Initialize-ProtectionAlertSet -NotifyUser 'alert@contoso.com -EmailCulture 'de-DE'
Create good practice Ruleset of Office 365 Protection Alert's and send all alerts in German to 'alert@contoso.com'
.NOTES
Please Review all the settings carefully before your run the script!
You must have a connection to the following Office 365 services:
- Security and Compliance Center
All features should work with your default Office 365 Enterprise plan. Business plans are not tested!
PLEASE NOTE:
This is really just a basic setup. It does NOT replace an security advice by a security consultant!
.LINK
New-ProtectionAlert
#>
[CmdletBinding(ConfirmImpact = 'Low')]
param
(
[Parameter(Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName,
HelpMessage = 'The NotifyUser parameter specifies the email addressesfor notification messages.')]
[ValidateNotNullOrEmpty()]
[Alias('AlertMail')]
[string[]]
$NotifyUser,
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[cultureinfo]
$EmailCulture = 'en-US'
)
begin
{
if (-not (Get-Command -Name New-ProtectionAlert))
{
Write-Error -Exception 'Not connected to the Office 365 Security & Compliance Center' -Message 'Use ''Connect-IPPSSession'' to connect to the Office 365 Security & Compliance Center' -Category OperationStopped -RecommendedAction 'Use ''Connect-IPPSSession'' to connect to the Office 365 Security & Compliance Center' -ErrorAction Stop
exit 1
}
Write-Output -InputObject "Create good practice Ruleset of Office 365 Protection Alert's"
}
process
{
#region
try
{
# Office 365 E5 subscription or Office 365 E3 subscription with an Office 365 Threat Intelligence required
$paramNewProtectionAlert = @{
Name = 'OneDrive deleted item threshold reached'
Category = 'DataGovernance'
NotifyUser = $NotifyUser
ThreatType = 'Activity'
Description = 'OneDrive deleted item threshold exceeds 50 in an hour'
AggregationType = 'SimpleAggregation'
Operation = 'FileDeleted'
Severity = 'Medium'
Filter = "Activity.SiteUrl -like '*-my.sharepoint.com/personal/*'"
Threshold = 50
TimeWindow = 60
ErrorAction = 'Stop'
WarningAction = 'Continue'
}
New-ProtectionAlert @paramNewProtectionAlert
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewProtectionAlert = @{
Name = 'MailRedirect created'
Category = 'DataLossPrevention'
ThreatType = 'Activity'
Operation = 'MailRedirect'
Severity = 'Medium'
NotifyUser = $NotifyUser
AggregationType = 'None'
Description = 'Email forward created'
ErrorAction = 'Stop'
WarningAction = 'Continue'
}
New-ProtectionAlert @paramNewProtectionAlert
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewProtectionAlert = @{
Name = 'Granted Mailbox Permission'
Category = 'DataLossPrevention'
ThreatType = 'Activity'
Operation = 'AddMailboxPermission'
Severity = 'Medium'
NotifyUser = $NotifyUser
AggregationType = 'None'
Description = 'Granted Mailbox Permission'
ErrorAction = 'Stop'
WarningAction = 'Continue'
}
New-ProtectionAlert @paramNewProtectionAlert
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewProtectionAlert = @{
Name = 'Outbound Phishing'
Category = 'ThreatManagement'
NotifyUser = $NotifyUser
ThreatType = 'Phish'
Description = 'Alert Outbound Phishing detected'
AggregationType = 'none'
Operation = $null
Filter = "(Mail.IsSystemZap -eq '0') -and (Mail.Direction -eq 'Outbound')"
Severity = 'High'
ErrorAction = 'Stop'
WarningAction = 'Continue'
}
New-ProtectionAlert @paramNewProtectionAlert
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewProtectionAlert = @{
Name = 'Inbound Phishing'
Category = 'ThreatManagement'
NotifyUser = $NotifyUser
ThreatType = 'Phish'
Description = 'Alert Inbound Phishing detected'
AggregationType = 'none'
Operation = $null
Filter = "(Mail.IsSystemZap -eq '0') -and (Mail.Direction -eq 'Inbound')"
Severity = 'High'
ErrorAction = 'Stop'
WarningAction = 'Continue'
}
New-ProtectionAlert @paramNewProtectionAlert
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewProtectionAlert = @{
Name = 'Office 365 Group deleted'
Category = 'DataGovernance'
NotifyUser = $NotifyUser
ThreatType = 'Activity'
Description = 'Alert if Office 365 Group is deleted'
AggregationType = 'none'
Operation = 'GroupRemoved'
Severity = 'Medium'
ErrorAction = 'Stop'
WarningAction = 'Continue'
}
New-ProtectionAlert @paramNewProtectionAlert
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewProtectionAlert = @{
Name = 'Office 365 Group Created'
Category = 'DataGovernance'
NotifyUser = $NotifyUser
ThreatType = 'Activity'
Description = 'Alert if Office 365 Group is created'
AggregationType = 'none'
Operation = 'GroupCreated'
Severity = 'Medium'
ErrorAction = 'Stop'
WarningAction = 'Continue'
}
New-ProtectionAlert @paramNewProtectionAlert
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewProtectionAlert = @{
Name = 'Sharing Policy Changed'
Category = 'DataLossPrevention'
NotifyUser = $NotifyUser
ThreatType = 'Activity'
Description = 'Alert if Sharing Policy is changed'
AggregationType = 'none'
Operation = 'SharingPolicyChanged'
Severity = 'High'
ErrorAction = 'Stop'
WarningAction = 'Continue'
}
New-ProtectionAlert @paramNewProtectionAlert
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
#region
try
{
$paramNewProtectionAlert = @{
Name = 'Compromised Account Activity'
Category = 'DataLossPrevention'
NotifyUser = $NotifyUser
ThreatType = 'Activity'
Description = 'Alert if Compromised Account activity is detected'
AggregationType = 'none'
Operation = 'CompromisedAccount'
Severity = 'High'
ErrorAction = 'Stop'
WarningAction = 'Continue'
}
New-ProtectionAlert @paramNewProtectionAlert
}
catch
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
Write-Warning -Message $info.Exception -WarningAction Continue -ErrorAction SilentlyContinue
}
#endregion
}
end
{
Write-Output "Check the new Protection Alert's in your Office 365 Security & Compliance Center"
}
}
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER

View File

@@ -0,0 +1,246 @@
<#
.SYNOPSIS
Get the AzureAD Audit Sign-In Logs
.DESCRIPTION
Get the AzureAD Audit Sign-In Logs and create several CSV files
.PARAMETER Days
Days to search
.EXAMPLE
PS C:\> .\Invoke-GetAzureADAuditSignInLogs.ps1
Get the AzureAD Audit Sign-In Logs for the last 24 hours
.EXAMPLE
PS C:\> .\Invoke-GetAzureADAuditSignInLogs.ps1 -Days 10
Get the AzureAD Audit Sign-In Logs for the last 10 days
.LINK
Get-AzureADAuditSignInLogs
.NOTES
Initial Beta Version
#>
[CmdletBinding(ConfirmImpact = 'None')]
param
(
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[Alias('DaysToSearch')]
[int]
$Days = 1
)
begin
{
#region
if ($Days -lt 1)
{
Write-Error -Exception 'Value to low' -Message 'The given Days Value is below 1' -Category InvalidArgument -TargetObject $Days -RecommendedAction 'Select between 1 and 30' -ErrorAction Stop
Exit 1
}
if ($Days -gt 30)
{
Write-Error -Exception 'Value to high' -Message 'The given Days Value is above 30' -Category InvalidArgument -TargetObject $Days -RecommendedAction 'Select between 1 and 30' -ErrorAction Stop
Exit 1
}
#endregion
# You might want to tweak this a bit!
$null = (Disconnect-AzureAD -Confirm:$false -ErrorAction SilentlyContinue)
$null = (Remove-Module -Name AzureAD -Force -ErrorAction SilentlyContinue)
$null = (Import-Module -Name AzureADPreview -Force -ErrorAction SilentlyContinue)
$null = (Connect-AzureAD)
# Garbage Collection
[GC]::Collect()
# Cleanup
$filterAll = $null
$AzureAdSignInAll = $null
$AzureAdSignInFail = $null
$AzureAdSignInGood = $null
$AzureAdSignInAllCAfail = $null
$AzureAdSignInFailCAfail = $null
$AzureAdSignInGoodCAfail = $null
# Define some defaults
$StartDateRaw = ((Get-Date).addDays(-$Days))
$StartDate = ('{0}-{1}-{2}' -f $StartDateRaw.Year, $StartDateRaw.Month, $StartDateRaw.Day)
$StartDateRaw = $null
$EndDateRaw = (Get-Date)
$EndDate = ('{0}-{1}-{2}' -f $EndDateRaw.Year, $EndDateRaw.Month, $EndDateRaw.Day)
$EndDateRaw = $null
}
process
{
try
{
# Filtering
$filterAll = ('createdDateTime ge {0} and createdDateTime le {1}' -f $StartDate, $EndDate)
# Get the Logs
$AzureAdSignInAll = (Get-AzureADAuditSignInLogs -Filter $filterAll)
# Rest is done with filtering
$AzureAdSignInFail = ($AzureAdSignInAll | Where-Object -FilterScript {
$_.status.errorCode -ne 0
})
$AzureAdSignInGood = ($AzureAdSignInAll | Where-Object -FilterScript {
$_.status.errorCode -eq 0
})
#region StructureData
$AzureAdSignInGood = ($AzureAdSignInGood | Select-Object -Property CreatedDateTime, UserPrincipalName, RiskState, AppId, ClientAppUsed, IpAddress, @{
N = 'City'
E = {
$_.Location.City
}
}, @{
N = 'CountryOrRegion'
E = {
$_.Location.CountryOrRegion
}
}, @{
N = 'FailureReason'
E = {
$_.Status.FailureReason
}
}, ConditionalAccessStatus)
$AzureAdSignInAll = ($AzureAdSignInAll | Select-Object -Property CreatedDateTime, UserPrincipalName, RiskState, AppId, ClientAppUsed, IpAddress, @{
N = 'City'
E = {
$_.Location.City
}
}, @{
N = 'CountryOrRegion'
E = {
$_.Location.CountryOrRegion
}
}, @{
N = 'FailureReason'
E = {
$_.Status.FailureReason
}
}, ConditionalAccessStatus)
$AzureAdSignInFail = ($AzureAdSignInFail | Select-Object -Property CreatedDateTime, UserPrincipalName, RiskState, AppId, ClientAppUsed, IpAddress, @{
N = 'City'
E = {
$_.Location.City
}
}, @{
N = 'CountryOrRegion'
E = {
$_.Location.CountryOrRegion
}
}, @{
N = 'FailureReason'
E = {
$_.Status.FailureReason
}
}, ConditionalAccessStatus)
#endregion StructureData
#region ConditionalAccessFilter
# BUG: Does not work as expected
$AzureAdSignInAllCAfail = ($AzureAdSignInAll | Where-Object -FilterScript {
(($_.ConditionalAccessStatus -ne 'success') -and ($_.ConditionalAccessStatus -ne 'notApplied'))
})
$AzureAdSignInFailCAfail = ($AzureAdSignInFail | Where-Object -FilterScript {
(($_.ConditionalAccessStatus -ne 'success') -and ($_.ConditionalAccessStatus -ne 'notApplied'))
})
$AzureAdSignInGoodCAfail = ($AzureAdSignInGood | Where-Object -FilterScript {
(($_.ConditionalAccessStatus -ne 'success') -and ($_.ConditionalAccessStatus -ne 'notApplied'))
})
#endregion ConditionalAccessFilter
$TimeStamp = Get-Date -Format yyyyMMdd_HHmmss
# TODO: Make it a parameter
$ExportPath = ('C:\scripts\PowerShell\exports\AzureADSignInAudit')
if (-not (Test-Path -Path $ExportPath))
{
$null = (New-Item -Path $ExportPath -ItemType Directory -Force)
}
#region Export
$null = ($AzureAdSignInAll | Export-Csv -Path ($ExportPath + '\AllSignInAuditLogs_' + $TimeStamp + '.csv') -NoTypeInformation -Force -Encoding UTF8)
$null = ($AzureAdSignInFail | Export-Csv -Path ($ExportPath + '\FailSignInAuditLogs_' + $TimeStamp + '.csv') -NoTypeInformation -Force -Encoding UTF8)
$null = ($AzureAdSignInGood | Export-Csv -Path ($ExportPath + '\GoodSignInAuditLogs_' + $TimeStamp + '.csv') -NoTypeInformation -Force -Encoding UTF8)
if ($AzureAdSignInAllCAfail)
{
$null = ($AzureAdSignInAllCAfail | Export-Csv -Path ($ExportPath + '\AllSignInAuditLogs_CAFAIL_' + $TimeStamp + '.csv') -NoTypeInformation -Force -Encoding UTF8)
}
if ($AzureAdSignInFailCAfail)
{
$null = ($AzureAdSignInFailCAfail | Export-Csv -Path ($ExportPath + '\FailSignInAuditLogs_CAFAIL_' + $TimeStamp + '.csv') -NoTypeInformation -Force -Encoding UTF8)
}
if ($AzureAdSignInGoodCAfail)
{
$null = ($AzureAdSignInGoodCAfail | Export-Csv -Path ($ExportPath + '\GoodSignInAuditLogs_CAFAIL_' + $TimeStamp + '.csv') -NoTypeInformation -Force -Encoding UTF8)
}
#endregion Export
}
catch
{
#region ErrorHandler
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
$paramWriteError = @{
Message = $e.Exception.Message
ErrorAction = 'Stop'
Exception = $e.Exception
TargetObject = $e.CategoryInfo.TargetName
}
Write-Error @paramWriteError
# Only here to catch a global ErrorAction overwrite
exit 1
#endregion ErrorHandler
}
finally
{
# Cleanup
$filterAll = $null
$AzureAdSignInAll = $null
$AzureAdSignInFail = $null
$AzureAdSignInGood = $null
$AzureAdSignInAllCAfail = $null
$AzureAdSignInFailCAfail = $null
$AzureAdSignInGoodCAfail = $null
# Garbage Collection
[GC]::Collect()
}
}

View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2021, enabling Technology <http://enatec.io>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,101 @@
<#
.SYNOPSIS
Replace the Domain for all UnifiedGroups (and Microsoft Teams) Primary SMTP Address
.DESCRIPTION
Replace the Domain for all UnifiedGroups (and Microsoft Teams) Primary SMTP Address
.PARAMETER OldDomain
The old Domain (e.g. contoso.com)
.PARAMETER NewDomain
The new Domain (e.g. contoso.net)
.EXAMPLE
PS C:\> .\ReplaceDomainForAllUnifiedGroups.ps1 -OldDomain 'contoso.com' -NewDomain 'contoso.net'
Replace the Primary SMTP Addresses for all UnifiedGroups (and Microsoft Teams) that are in the domain 'contoso.com' with the someone in the Domain 'contoso.net'
e.g. if an old address was myTeam@contoso.com would end up as myTeam@contoso.new
.LINK
https://docs.microsoft.com/en-us/powershell/exchange/exchange-online/connect-to-exchange-online-powershell/connect-to-exchange-online-powershell?view=exchange-ps
.LINK
http://hochwald.net
.NOTES
Quick and dirty approach, without any real Error handling.
A friend asked me for a solution after a merger to replace all Primary SMTP Addresses and get rif of the old domain (legal requirement in this case)
You need be be connected to an Exchange Online Session (NOT part of this script).
#>
[CmdletBinding(ConfirmImpact = 'Low',
SupportsShouldProcess = $true)]
param
(
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[Alias('DomainToReplace')]
[string]
$OldDomain,
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[string]
$NewDomain
)
begin
{
$OldMailFilter = ('@' + $OldDomain)
# Cleanup
$AllUnifiedGroups = $null
}
process
{
$AllUnifiedGroups = (Get-UnifiedGroup | Where-Object -FilterScript {
$_.PrimarySmtpAddress -like ('*' + $OldMailFilter)
} | Select-Object -Property Identity, DisplayName, PrimarySmtpAddress)
if ($AllUnifiedGroups)
{
foreach ($item in $AllUnifiedGroups)
{
if ($item.PrimarySmtpAddress -like ('*' + $OldMailFilter))
{
$OldMailAddress = $null
$OldMailAddress = (($item).PrimarySmtpAddress)
$NewMailAddress = $null
$NewMailAddress = ($OldMailAddress.Replace($OldMailFilter, ('@' + $NewDomain)))
Write-Verbose -Message ('Replace: {0} with: {1}' -f $OldMailAddress, $NewMailAddress)
# Add the new Address
$null = (Set-UnifiedGroup -Identity (($item).Identity) -EmailAddresses: @{
Add = $NewMailAddress
} -Confirm:$false)
# Make new Address the primary SMTP address
$null = (Set-UnifiedGroup -Identity (($item).Identity) -PrimarySmtpAddress $NewMailAddress -Confirm:$false)
# Remove the old SMTP Address
$null = (Set-UnifiedGroup -Identity (($item).Identity) -EmailAddresses: @{
Remove = $OldMailAddress
} -Confirm:$false)
}
else
{
Write-Warning -Message ('Sorry, the PrimarySmtpAddress of {0} is not in {1}' -f $item.DisplayName, $OldDomain)
}
}
}
else
{
Write-Output -InputObject 'Nothing to do!!!'
}
}

View File

@@ -0,0 +1,220 @@
#requires -Version 3.0 -Modules AzureADPreview
<#
.SYNOPSIS
Create or modify a Azure AD Naming Policy for Office 365 Groups
.DESCRIPTION
Create or modify a Azure AD Naming Policy for Office 365 Groups, these groups (a/k/a Unified Groups) are the base for Microsoft Teams and other Microsoft 365 services.
.PARAMETER BlockedWordsFile
CSV with your blacklisted names, 5.000 word is the Office 365 maximum
.PARAMETER ApplyDefaults
Apply some basics and defaults
.EXAMPLE
PS C:\> .\Set-AzureADNamingPolicyForOffice365Groups.ps1
Create or modify a Azure AD Naming Policy for Office 365 Groups
.EXAMPLE
PS C:\> .\Set-AzureADNamingPolicyForOffice365Groups.ps1 -Verbose
Create or modify a Azure AD Naming Policy for Office 365 Groups
.EXAMPLE
PS C:\> .\Set-AzureADNamingPolicyForOffice365Groups.ps1 -ApplyDefaults
Create or modify a Azure AD Naming Policy for Office 365 Groups and apply some basics and defaults
.EXAMPLE
PS C:\> .\Set-AzureADNamingPolicyForOffice365Groups.ps1 -ApplyDefaults -Verbose
Create or modify a Azure AD Naming Policy for Office 365 Groups and apply some basics and defaults
.NOTES
Nothing fancy, just a modified version of the Microsoft script.
Please review the setting and check if my values match your requirements.
If you create the new Group "Development", the Name becomes:
GRP_Development_Frankfurt
This is based on my default naming convention: 'GRP_[GroupName]_[Office]' - Change it below to match your own naming convention!
If you create the new Group "Payroll" it will fail! The Word "Payroll" is blacklisted!
Please note: You need to have a AzureAD Premium P1 (or Higher) License, or any license option that contains AzureAD Premium P1 or P2
.LINK
https://docs.microsoft.com/en-us/microsoft-365/admin/create-groups/groups-naming-policy?view=o365-worldwide#how-to-set-up-the-naming-policy-in-azure-ad-powershell
#>
[CmdletBinding(ConfirmImpact = 'Low')]
param
(
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[AllowNull()]
[AllowEmptyString()]
[Alias('File', 'Path')]
[string]
$BlockedWordsFile = '.\BlockedWords.csv',
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[switch]
$ApplyDefaults
)
begin
{
# Remove the regular Module
$paramRemoveModule = @{
Name = 'AzureAD'
Force = $true
ErrorAction = 'SilentlyContinue'
WarningAction = 'SilentlyContinue'
}
$null = (Remove-Module @paramRemoveModule)
# Do we have a CSV File?
if (Test-Path -Path $BlockedWordsFile -ErrorAction SilentlyContinue)
{
# Fine, let us import the CSV File
$paramImportCsv = @{
Path = $BlockedWordsFile
Encoding = 'UTF8'
ErrorAction = 'Stop'
}
$BlockedWordsImport = (Import-Csv @paramImportCsv)
# Transfer the values into the list
[string]$BlockedWords = ($BlockedWordsImport.BlockedWords -join ', ')
# Cleanup
$BlockedWordsImport = $null
}
else
{
# No CSV, let us use some defaults
[string]$BlockedWords = 'Payroll,CEO,HR,hochwald'
}
# Prefix and Suffix for the Unified Groups
<#
Valid suffix values are:
[Company]
[CountryOrRegion]
[Department]
[Office]
[StateOrProvince]
[Title]
#>
$PrefixSuffix = 'GRP_[GroupName]_[Office]'
# Connect to your AzureAD tenant, if needed
try
{
$null = (Get-AzureADDomain -ErrorAction Stop)
}
catch
{
$null = (Connect-AzureAD)
}
}
process
{
try
{
# Get the existing template
$template = (Get-AzureADDirectorySettingTemplate -ErrorAction Stop | Where-Object -FilterScript {
$_.displayname -eq 'group.unified'
})
# Modify the settings
$settingsCopy = $template.CreateDirectorySetting()
# Create a new setting
$paramNewAzureADDirectorySetting = @{
DirectorySetting = $settingsCopy
ErrorAction = 'Stop'
}
$null = (New-AzureADDirectorySetting @paramNewAzureADDirectorySetting)
}
catch
{
Write-Verbose -Message 'Looks like we have the Settings...'
}
finally
{
# Get the settings
$settingsObjectID = (Get-AzureADDirectorySetting | Where-Object -Property Displayname -Value 'Group.Unified' -EQ | Select-Object -ExpandProperty id)
}
# Read the settings
$settingsCopy = (Get-AzureADDirectorySetting -Id $settingsObjectID)
# Modify the settings
$settingsCopy['PrefixSuffixNamingRequirement'] = $PrefixSuffix
$settingsCopy['CustomBlockedWordsList'] = $BlockedWords
# Apply some basics and defaults
if ($ApplyDefaults)
{
$settingsCopy['EnableMSStandardBlockedWords'] = $true
$settingsCopy['AllowGuestsToBeGroupOwner'] = $false
$settingsCopy['AllowGuestsToAccessGroups'] = $true
}
# Apply the settings
$paramSetAzureADDirectorySetting = @{
Id = $settingsObjectID
DirectorySetting = $settingsCopy
ErrorAction = 'Stop'
}
$null = (Set-AzureADDirectorySetting @paramSetAzureADDirectorySetting)
}
end
{
# Get the Info
$Info = (Get-AzureADDirectorySetting -Id $settingsObjectID | Select-Object -ExpandProperty Values)
# Dump the Info
$Info
# Cleanup
$Info = $null
}
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER

View File

@@ -0,0 +1,284 @@
#requires -Version 3.0 -Modules AzureAD, WhiteboardAdmin
function Set-MicrosoftNewWhiteboardOwner
{
<#
.SYNOPSIS
Set the owner for a a given Microsoft Whiteboard
.DESCRIPTION
Set the owner for a a given Microsoft Whiteboard
.PARAMETER WhiteboardId
The Whiteboard for which the owner is being changed.
.PARAMETER OwnerId
The ID of the previous owner.
.PARAMETER OwnerName
The UserPrincipalName of the previous owner.
.PARAMETER NewOwnerId
The ID of the new owner.
.PARAMETER NewOwnerName
The UserPrincipalName of the new owner.
.PARAMETER All
Transfer ownership of all Whiteboards owned by a user to another user.
.EXAMPLE
PS C:\> Set-MicrosoftNewWhiteboardOwner -WhiteboardId 'ad8d4e1b-45c9-4ff8-9757-77086f1b3fec' -OwnerId 'c85245cf-d5d9-4286-968f-6f95d46a885f' -NewOwnerId '7ff1141a-d6fa-401b-a7e5-0f8c6dd64aba'
Transfers the ownership of the Whiteboard with the ID 'ad8d4e1b-45c9-4ff8-9757-77086f1b3fec' from UserID 'c85245cf-d5d9-4286-968f-6f95d46a885f' the the new owner with the ID '7ff1141a-d6fa-401b-a7e5-0f8c6dd64aba'
.EXAMPLE
PS C:\> Set-MicrosoftNewWhiteboardOwner -WhiteboardId 'ad8d4e1b-45c9-4ff8-9757-77086f1b3fec' -OwnerName 'john.doe@contoso.com' -NewOwnerName 'jane.doe@contoso.com'
Transfers the ownership of the Whiteboard with the ID 'ad8d4e1b-45c9-4ff8-9757-77086f1b3fec' from User 'john.doe@contoso.com' the the new owner 'jane.doe@contoso.com'
.EXAMPLE
PS C:\> Set-MicrosoftNewWhiteboardOwner -All -OwnerId 'c85245cf-d5d9-4286-968f-6f95d46a885f' -NewOwnerId '7ff1141a-d6fa-401b-a7e5-0f8c6dd64aba'
Transfers the ownership of the Whiteboards from the Owner with the ID 'c85245cf-d5d9-4286-968f-6f95d46a885f' the the new owner with the ID '7ff1141a-d6fa-401b-a7e5-0f8c6dd64aba'
This might be a use case for off boarding
.EXAMPLE
PS C:\> Set-MicrosoftNewWhiteboardOwner -All -OwnerName 'john.doe@contoso.com' -NewOwnerName 'jane.doe@contoso.com'
Transfers the ownership of the Whiteboards from the Owner 'john.doe@contoso.com' the the new owner 'jane.doe@contoso.com'
This might be a use case for off boarding
.OUTPUTS
bool
.NOTES
Hard to automate: The WhiteboardAdmin does not have a connect function and will always prompt for auth (and then cache the credentials used to connect).
All transfered Whiteboards are then shared between both, the old and the new owner!
.LINK
Get-MicrosoftWhiteboardReport
.LINK
Invoke-TransferAllWhiteboards
.LINK
Set-WhiteboardOwner
.LINK
https://www.powershellgallery.com/packages/WhiteboardAdmin/
#>
[CmdletBinding(DefaultParameterSetName = 'UseID',
ConfirmImpact = 'Low',
SupportsShouldProcess)]
[OutputType([bool], ParameterSetName = 'UseID')]
[OutputType([bool], ParameterSetName = 'UseName')]
[OutputType([bool])]
param
(
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[Alias('Whiteboard')]
[string]
$WhiteboardId = $null,
[Parameter(ParameterSetName = 'UseID',
Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName,
HelpMessage = 'The ID of the previous owner.')]
[ValidateNotNullOrEmpty()]
[Alias('OldOwnerId')]
[string]
$OwnerId,
[Parameter(ParameterSetName = 'UseName',
Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName,
HelpMessage = 'The UserPrincipalName of the previous owner.')]
[Alias('OwnerUserPrincipalName', 'OwnerUserPrincipal')]
[string]
$OwnerName,
[Parameter(ParameterSetName = 'UseID',
Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName,
HelpMessage = 'The ID of the new owner.')]
[ValidateNotNullOrEmpty()]
[string]
$NewOwnerId,
[Parameter(ParameterSetName = 'UseName', HelpMessage = 'The UserPrincipalName of the new owner.',
Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[Alias('NewOwnerUserPrincipalName', 'NewOwnerUserPrincipal')]
[string]
$NewOwnerName,
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[switch]
$All
)
begin
{
# Check if one of the required parameters is present
if ((-not (($PsCmdlet.MyInvocation.BoundParameters['All']))) -and (-not ($WhiteboardId)))
{
Write-Error -Message 'No WhiteboardId to transfer found and -All was not given!' -Category NotSpecified -TargetObject $WhiteboardId -RecommendedAction 'Specify -All or the WhiteboardId to transfer' -ErrorAction Stop
# Only here to catch a global ErrorAction overwrite
exit 1
}
try
{
try
{
$null = (Get-AzureADTenantDetail -ErrorAction Stop)
}
catch
{
Connect-AzureAD
}
}
catch
{
#region ErrorHandler
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
$paramWriteError = @{
Message = $e.Exception.Message
ErrorAction = 'Stop'
Exception = $e.Exception
TargetObject = $e.CategoryInfo.TargetName
}
Write-Error @paramWriteError
# Only here to catch a global ErrorAction overwrite
exit 1
#endregion ErrorHandler
}
}
process
{
$TargetObject = if (($PsCmdlet.MyInvocation.BoundParameters['All']).IsPresent)
{
'all Whiteboards'
}
else
{
$WhiteboardId
}
if ($PsCmdlet.ShouldProcess($TargetObject, 'Transfer ownership'))
{
try
{
switch ($PsCmdlet.ParameterSetName)
{
'UseID'
{
if (($PsCmdlet.MyInvocation.BoundParameters['All']).IsPresent)
{
Invoke-TransferAllWhiteboards -OldOwnerId $OwnerId -NewOwnerId $NewOwnerId -ErrorAction Stop -Confirm:$false
}
else
{
Set-WhiteboardOwner -WhiteboardId $WhiteboardId -OldOwnerId $OwnerId -NewOwnerId $NewOwnerId -ErrorAction Stop -Confirm:$false
}
break
}
'UseName'
{
$OwnerId = (Get-AzureADUser -Filter ("userPrincipalName eq '{0}'" -f $OwnerName) | Select-Object -ExpandProperty ObjectId)
$NewOwnerId = (Get-AzureADUser -Filter ("userPrincipalName eq '{0}'" -f $NewOwnerName) | Select-Object -ExpandProperty ObjectId)
if (($PsCmdlet.MyInvocation.BoundParameters['All']).IsPresent)
{
Invoke-TransferAllWhiteboards -OldOwnerId $OwnerId -NewOwnerId $NewOwnerId -ErrorAction Stop -Confirm:$false
}
else
{
Set-WhiteboardOwner -WhiteboardId $WhiteboardId -OldOwnerId $OwnerId -NewOwnerId $NewOwnerId -ErrorAction Stop -Confirm:$false
}
break
}
}
}
catch
{
#region ErrorHandler
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]@{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info | Out-String | Write-Verbose
$paramWriteError = @{
Message = $e.Exception.Message
ErrorAction = 'Stop'
Exception = $e.Exception
TargetObject = $e.CategoryInfo.TargetName
}
Write-Error @paramWriteError
# Only here to catch a global ErrorAction overwrite
exit 1
#endregion ErrorHandler
}
}
}
}
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER

View File

@@ -0,0 +1,199 @@
function Set-Office365GroupMailAddress
{
<#
.SYNOPSIS
Add or change Office 365 Group or Team Email Address
.DESCRIPTION
Add or change Office 365 Group or Team Email Address
.PARAMETER OldDomain
Old Domain Name, Format is: DOMAIN.TLD
e.g. contoso.com
.PARAMETER NewDomain
NEW Domain Name, Format is: DOMAIN.TLD
e.g. contoso.net
.PARAMETER MakeNewPrimary
Will the new Mail address be the new Primary SMTP Address?
.PARAMETER RemoveOld
Should the old Primary SMTP Address be removed?
Please note: You must make another one to your Primary SMTP Address before you do this!
.EXAMPLE
PS C:\> Set-Office365GroupMailAddress -OldDomain 'contoso.onmicrosoft.com' -NewDomain 'contoso.com'
If the existing Primary SMTP Address is 'dummy@contoso.onmicrosoft.com', this will add 'dummy@contoso.com' as alias.
.EXAMPLE
PS C:\> Set-Office365GroupMailAddress -OldDomain 'contoso.com' -NewDomain 'contoso.net' -MakeNewPrimary
If the existing Primary SMTP Address is 'dummy@contoso.com', this will add 'dummy@contoso.com' as alias,
and make it the new Primary SMTP Address
.EXAMPLE
PS C:\> Set-Office365GroupMailAddress -OldDomain 'contoso.com' -NewDomain 'contoso.net' -MakeNewPrimary -RemoveOld
If the existing Primary SMTP Address is 'dummy@contoso.com', this will add 'dummy@contoso.com' as alias,
and make it the new Primary SMTP Address and removes the old address
.EXAMPLE
PS C:\> Set-Office365GroupMailAddress -OldDomain 'contoso.com' -NewDomain 'contoso.net' -MakeNewPrimary -RemoveOld -WhatIf
If the existing Primary SMTP Address is 'dummy@contoso.com', this will add 'dummy@contoso.com' as alias,
and make it the new Primary SMTP Address and removes the old address
Will simulate the execution (WhatIf is present)
.EXAMPLE
PS C:\> Set-Office365GroupMailAddress -OldDomain 'contoso.com' -NewDomain 'contoso.net' -MakeNewPrimary -RemoveOld -Verbose
If the existing Primary SMTP Address is 'dummy@contoso.com', this will add 'dummy@contoso.com' as alias,
and make it the new Primary SMTP Address and removes the old address.
The Process will be verbose (Verbose is present)
.NOTES
Initial public Release!
Might become handy if you like to convert all DOMAIN.onmicrosoft.com addresses to your own domain,
or if you decide to go with a new external mail domain.
#>
[CmdletBinding(ConfirmImpact = 'Low',
SupportsShouldProcess)]
param
(
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[string]
$OldDomain = 'contoso.com',
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[string]
$NewDomain = 'contoso.net',
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[Alias('MakePrimary')]
[switch]
$MakeNewPrimary = $null,
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[Alias('RemoveOldDomain', 'Remove', 'Cleanup')]
[switch]
$RemoveOld = $null
)
begin
{
# Save the infos from the switches
$IsVerbose = (($PSCmdlet.MyInvocation.BoundParameters['Verbose']).IsPresent)
$IsWhatIf = (($PSCmdlet.MyInvocation.BoundParameters['WhatIf']).IsPresent)
# Build the filter strings
$OldDomainString = ('@' + $OldDomain)
$NewDomainString = ('@' + $NewDomain)
# Cleanup
$WrongGroups = $null
# Get all matching Groups
$WrongGroups = (Get-UnifiedGroup -ErrorAction Stop -Verbose:$IsVerbose | Where-Object -FilterScript {
$_.PrimarySmtpAddress -like ('*' + $OldDomainString)
})
}
process
{
foreach ($item in $WrongGroups)
{
# Replace within the string
$NewPrimarySmtpAddress = ($item.PrimarySmtpAddress).Replace($OldDomainString, $NewDomainString)
#region AddEmailAddresses
$paramSetUnifiedGroup = @{
Identity = ($item.Name)
EmailAddresses = @{
Add = $NewPrimarySmtpAddress
}
ErrorAction = 'Continue'
WhatIf = $IsWhatIf
Verbose = $IsVerbose
Confirm = $false
}
$null = (Set-UnifiedGroup @paramSetUnifiedGroup)
#endregion AddEmailAddresses
#region SetPrimarySmtpAddress
if ($MakeNewPrimary)
{
$paramSetUnifiedGroup = @{
Identity = ($item.Name)
PrimarySmtpAddress = $NewPrimarySmtpAddress
ErrorAction = 'Continue'
WhatIf = $IsWhatIf
Verbose = $IsVerbose
Confirm = $false
}
$null = (Set-UnifiedGroup @paramSetUnifiedGroup)
}
#endregion SetPrimarySmtpAddress
#region RemoveOldAddress
if ($RemoveOld)
{
$paramSetUnifiedGroup = @{
Identity = ($item.Name)
EmailAddresses = @{
Remove = ($item.PrimarySmtpAddress)
}
ErrorAction = 'Continue'
WhatIf = $IsWhatIf
Verbose = $IsVerbose
Confirm = $false
}
$null = (Set-UnifiedGroup @paramSetUnifiedGroup)
}
#endregion RemoveOldAddress
}
}
end
{
# Cleanup
$WrongGroups = $null
}
}
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER

View File

@@ -0,0 +1,135 @@
#requires -Version 3.0 -Modules MSOnline
function Set-bdcMsolMFAState
{
<#
.SYNOPSIS
Convert users from per-user MFA to Conditional Access based MFA
.DESCRIPTION
Convert users from per-user MFA to Conditional Access based MFA
.PARAMETER ObjectId
ObjectId of the Office 365 User
.PARAMETER UserPrincipalName
User Principal Name of the Office 365 User
.PARAMETER State
MFA State ('Disabled','Enabled', or 'Enforced')
Default is Disabled
.EXAMPLE
Set-bdcMsolMFAState -ObjectId Value -UserPrincipalName john.doe@contoso.com -State Enabled
Enabled MFA for john.doe@contoso.com
.EXAMPLE
Set-bdcMsolMFAState -ObjectId Value -UserPrincipalName john.doe@contoso.com -State Enabled
Enforces MFA for john.doe@contoso.com
.EXAMPLE
Set-bdcMsolMFAState -ObjectId Value -UserPrincipalName john.doe@contoso.com -State Enabled
Disables MFA for john.doe@contoso.com
.EXAMPLE
Get-MsolUser -All | Set-bdcMsolMFAState -State Disabled
Disable MFA for all users
.EXAMPLE
(Get-MsolUser -UserPrincipalName john.doe@contoso.com | Select-Object -Property UserPrincipalName,StrongAuthenticationRequirements)
Check the MFA state for john.doe@contoso.com
.EXAMPLE
(Get-MsolUser -UserPrincipalName john.doe@contoso.com | Select-Object -Property UserPrincipalName,StrongAuthenticationRequirements).StrongAuthenticationRequirements
Check the MFA details for john.doe@contoso.com
.OUTPUTS
None
.NOTES
Just a minor tweaked version of the original Microsoft version (See link below)
.LINK
https://docs.microsoft.com/en-us/azure/active-directory/authentication/howto-mfa-userstates
.INPUTS
String
#>
[CmdletBinding(ConfirmImpact = 'medium',
SupportsShouldProcess)]
param
(
[Parameter(ValueFromPipelineByPropertyName)]
[string]
$ObjectId = $null,
[Parameter(ValueFromPipelineByPropertyName)]
[string]
$UserPrincipalName = $null,
[ValidateSet('Disabled', 'Enabled', 'Enforced')]
[string]
$State = 'Disabled'
)
begin
{
# Load the Assembly
$null = (Add-Type -AssemblyName Microsoft.Online.Administration.Automation.PSModule)
}
process
{
Write-Verbose -Message ('Setting MFA state for user ' + $UserPrincipalName + ' (' + $ObjectId + ') to ' + $State)
# Create a new Object
$Requirements = @()
# Create the settings and add them to the new object
if ($State -ne 'Disabled')
{
$Requirement = [Microsoft.Online.Administration.StrongAuthenticationRequirement]::new()
$Requirement.RelyingParty = '*'
$Requirement.State = $State
$Requirements += $Requirement
}
# Apply the new settings, based on the Object
$null = (Set-MsolUser -ObjectId $ObjectId -UserPrincipalName $UserPrincipalName -StrongAuthenticationRequirements $Requirements)
}
end
{
# Cleanup
$Requirements = $null
}
}
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER

View File

@@ -0,0 +1,56 @@
# Get your active SafeLinks Policy/Policies
Get-SafeLinksPolicy | Where-Object -FilterScript {
$_.IsEnabled -eq $true
} | Select-Object -ExpandProperty Identity
# I use the default policy in this example
$SafeLinksPolicyIdentity = 'Recommended safe links policy'
# A list of URLs to exclude from rewriting
$DoNotRewriteUrls = @(
'*.webex.com/*'
'*.zoom.us/*'
'zoom.us/*'
'*.teams.microsoft.com/*'
'zoom.com/*'
'*.zoom.com/*'
'teams.microsoft.com/*'
)
# I use the default policy in this example
Set-SafeLinksPolicy -Identity $SafeLinksPolicyIdentity -DoNotRewriteUrls $DoNotRewriteUrls
<#
Note:
The WhiteListedUrls and ExcludedUrls parameters are deprecated
Only use the DoNotRewriteUrls parameter
#>
# Remove all URLs from the SafeLinks Policy/Policies
$DoNotRewriteUrls = @()
Set-SafeLinksPolicy -Identity $SafeLinksPolicyIdentity -DoNotRewriteUrls $DoNotRewriteUrls
# Apply my recommended settings to the policy/policies
$paramSetSafeLinksPolicy = @{
Identity = $SafeLinksPolicyIdentity
DoNotTrackUserClicks = $false
DoNotAllowClickThrough = $true
ScanUrls = $true
EnableForInternalSenders = $true
DeliverMessageAfterScan = $true
}
Set-SafeLinksPolicy @paramSetSafeLinksPolicy
<#
Identity = The Identity parameter specifies the Safe Links policy that you want to modify
DoNotTrackUserClicks = Track user clicks related to links in email messages and Microsoft Teams
DoNotAllowClickThrough = Disallow users to click through to the original URL
ScanUrls = Enable real-time scanning of links in email messages
EnableForInternalSenders = The policy is applied to internal and external senders
DeliverMessageAfterScan = Wait until Safe Links scanning is complete before delivering the message
#>
# This is fine, no panic:
# WARNING: The command completed successfully but no settings of 'Recommended safe links policy' have been modified.
# Do this for all your Teams Room Devices that should except external invitations
Set-CalendarProcessing -Identity '<Your Device here>' -ProcessExternalMeetingMessages $true

View File

@@ -0,0 +1,977 @@
#requires -Version 2.0
<#
.SYNOPSIS
Bootstrap a Office 365 Tenant
.DESCRIPTION
Bootstrap a Office 365 Tenant
It Applies some of the enabling Technology best practice settings, mostly related to security and Exchange Online.
.NOTES
Please Review all the settings carefully before your run the script!
You must have a connection to the following Office 365 services:
- Skype for Business Online
- Exchange Online
- Security and Compliance Center
- AzureAD (Regular Module or Preview)
All features should work with your default Office 365 Enterprise plan. Business plans are not tested!
PLEASE NOTE:
This is really just a basic setup. It does NOT replace an security advice by a security consultant!
It should elevate your Security score a bit. But you still need to configure a bit more (manually or by other scripts)!
.LINK
https://hochwald.net/office-365-minimum-security-baseline/
.LINK
http://www.enatec.io
#>
[CmdletBinding(ConfirmImpact = 'Low')]
param ()
begin
{
# Variables
[string[]]$AdminMail = 'support@contoso.com'
[string]$EmailCulture = 'en-US'
#region Defaults
[string]$SCT = 'SilentlyContinue'
[string]$STP = 'Stop'
#endregion Defaults
}
process
{
#region
# Enable Unified audit log
$paramGetAdminAuditLogConfig = @{
ErrorAction = $SCT
WarningAction = $SCT
}
if (((Get-AdminAuditLogConfig @paramGetAdminAuditLogConfig) | Select-Object -ExpandProperty UnifiedAuditLogIngestionEnabled) -ne $true)
{
try
{
$paramSetAdminAuditLogConfig = @{
UnifiedAuditLogIngestionEnabled = $true
ErrorAction = $SCT
WarningAction = $STP
}
$null = (Set-AdminAuditLogConfig @paramSetAdminAuditLogConfig)
Write-Output -InputObject 'Unified audit log is enabled'
}
catch
{
Write-Warning -Message 'Unable to enable Unified audit log'
}
}
else
{
Write-Output -InputObject 'Unified audit log is already enabled'
}
$paramGetAdminAuditLogConfig = $null
$paramSetAdminAuditLogConfig = $null
#endregion
#region
# Enable Admin audit log
$paramGetAdminAuditLogConfig = @{
ErrorAction = $SCT
WarningAction = $SCT
}
if (((Get-AdminAuditLogConfig @paramGetAdminAuditLogConfig) | Select-Object -ExpandProperty AdminAuditLogEnabled) -ne $true)
{
try
{
$paramSetAdminAuditLogConfig = @{
AdminAuditLogEnabled = $true
AdminAuditLogCmdlets = '*'
AdminAuditLogParameters = '*'
ErrorAction = $STP
WarningAction = $SCT
}
$null = (Set-AdminAuditLogConfig @paramSetAdminAuditLogConfig)
Write-Output -InputObject 'Admin audit log enabled'
}
catch
{
Write-Warning -Message 'Unable to enable Admin audit log'
}
}
else
{
Write-Output -InputObject 'Admin audit log is already enabled'
}
$paramGetAdminAuditLogConfig = $null
$paramSetAdminAuditLogConfig = $null
#endregion
#region
# Enable Mailbox Audit Logging for all mailboxes
$paramGetOrganizationConfig = @{
ErrorAction = $SCT
WarningAction = $SCT
}
if (((Get-OrganizationConfig @paramGetOrganizationConfig) | Select-Object -ExpandProperty AuditDisabled) -ne $false)
{
try
{
$paramSetOrganizationConfig = @{
AuditDisabled = $false
ErrorAction = $STP
WarningAction = $SCT
}
$null = (Set-OrganizationConfig @paramSetOrganizationConfig)
Write-Output -InputObject 'Mailbox Audit Logging enabled'
}
catch
{
Write-Warning -Message 'Unable to enable Mailbox Audit Logging'
}
}
else
{
Write-Output -InputObject 'Mailbox Audit Logging was already enabled'
}
$paramGetOrganizationConfig = $null
$paramSetOrganizationConfig = $null
#endregion
#region
# Block sign-in for all Shared, Room, and Equipment Mailboxes
<#
PLEASE NOTE:
If you use a resource like an Microsoft Teams Rooms, or a Surface Hub you might need to re-enable them afterwards!
Otherwise, your resource might not work as expected.
The same applies to multi-factor authentication (MFA)! You will need to exclude devices like this!
#>
$paramGetMailbox = @{
ResultSize = 'unlimited'
RecipientTypeDetails = 'SharedMailbox', 'RoomMailbox', 'EquipmentMailbox'
ErrorAction = $SCT
WarningAction = $SCT
}
$null = (Get-Mailbox @paramGetMailbox | Select-Object -ExpandProperty UserPrincipalName | ForEach-Object {
$paramSetAzureAdUser = @{
ObjectId = $_
AccountEnabled = $false
ErrorAction = $SCT
WarningAction = $SCT
}
Set-AzureADUser @paramSetAzureAdUser
})
#endregion
#region
# Apply and activate for each Mailbox
try
{
$paramGetMailbox = @{
ResultSize = 'Unlimited'
ErrorAction = $SCT
WarningAction = $SCT
}
$null = ((Get-Mailbox @paramGetMailbox) | Where-Object -FilterScript {
($_.RecipientTypeDetails -ne 'DiscoveryMailbox') -and ($_.AuditEnabled -ne $true)
} | ForEach-Object -Process {
$paramSetMailbox = @{
Identity = $_.UserPrincipalName
AuditEnabled = $true
ErrorAction = $SCT
WarningAction = $SCT
}
$null = (Set-Mailbox @paramSetMailbox)
})
Write-Output -InputObject 'Applied and activated audit for each Mailbox'
}
catch
{
Write-Warning -Message 'Unable to apply and/or activate audit for each Mailbox'
}
finally
{
$paramGetMailbox = $null
$paramSetMailbox = $null
}
#endregion
#region
# Disable POP3/IMAP4
try
{
$paramGetCASMailboxPlan = @{
Filter = {
ImapEnabled -eq 'true' -or PopEnabled -eq 'true'
}
ErrorAction = $SCT
WarningAction = $SCT
}
$paramSetCASMailboxPlan = @{
ImapEnabled = $false
PopEnabled = $false
ErrorAction = $STP
WarningAction = $SCT
}
$null = (Get-CASMailboxPlan @paramGetCASMailboxPlan | set-CASMailboxPlan @paramSetCASMailboxPlan)
Write-Output -InputObject 'POP3 and IMAP4 are disabled - CAS Mailbox Plan'
}
catch
{
Write-Warning -Message 'Unable to disable POP3 and IMAP4 - CAS Mailbox Plan'
}
finally
{
$paramGetCASMailboxPlan = $null
$paramSetCASMailboxPlan = $null
}
try
{
$paramGetCASMailbox = @{
Filter = {
ImapEnabled -eq 'true' -or PopEnabled -eq 'true'
}
ErrorAction = $SCT
WarningAction = $SCT
}
$paramSetCASMailbox = @{
ImapEnabled = $false
PopEnabled = $false
ErrorAction = $STP
WarningAction = $SCT
}
$null = (Get-CASMailbox @paramGetCASMailbox | Select-Object -Property @{
Name = 'Identity'
Expression = {
$_.PrimarySmtpAddress
}
} | Set-CASMailbox @paramSetCASMailbox)
Write-Output -InputObject 'POP3 and IMAP4 are disabled - CAS Mailbox'
}
catch
{
Write-Warning -Message 'Unable to disable POP3 and IMAP4 - CAS Mailbox'
}
finally
{
$paramGetCASMailbox = $null
$paramSetCASMailbox = $null
}
#endregion
#region
# Enable/Set End User Spam Notification
try
{
$ExistingHostedContentFilterPolicy = (Get-HostedContentFilterPolicy -Identity Default -ErrorAction $SCT -WarningAction $SCT | Select-Object -Property EndUserSpamNotificationFrequency, HighConfidenceSpamAction, EnableEndUserSpamNotifications)
if ((-not ($ExistingHostedContentFilterPolicy.EndUserSpamNotificationFrequency -eq 1)) -and (-not ($ExistingHostedContentFilterPolicy.HighConfidenceSpamAction -eq 'HighConfidenceSpamAction')) -and (-not ($ExistingHostedContentFilterPolicy.EnableEndUserSpamNotifications -eq $true)))
{
$paramSetHostedContentFilterPolicy = @{
Identity = 'Default'
EndUserSpamNotificationFrequency = 1
EndUserSpamNotificationLanguage = 'Default'
HighConfidenceSpamAction = 'Quarantine'
EnableEndUserSpamNotifications = $true
ErrorAction = $STP
WarningAction = $SCT
}
$null = (Set-HostedContentFilterPolicy @paramSetHostedContentFilterPolicy)
Write-Output -InputObject 'Hosted Content Filter Policy was fixed'
}
else
{
Write-Output -InputObject 'Hosted Content Filter Policy was not fixed'
}
}
catch
{
Write-Warning -Message 'Unable to fix Hosted Content Filter Policy'
}
finally
{
$ExistingHostedContentFilterPolicy = $null
$paramSetHostedContentFilterPolicy = $null
}
#endregion
#region
# Enable/Set Outbound Spam Filter Notification
try
{
$ExistingHostedOutboundSpamFilterPolicy = (Get-HostedOutboundSpamFilterPolicy -Identity Default -ErrorAction $SCT -WarningAction $SCT | Select-Object -Property NotifyOutboundSpamRecipients, NotifyOutboundSpam)
if ((-not ($ExistingHostedOutboundSpamFilterPolicy.NotifyOutboundSpamRecipients -eq $AdminMail)) -and (-not ($ExistingHostedOutboundSpamFilterPolicy.NotifyOutboundSpam -eq $true)))
{
$paramSetHostedOutboundSpamFilterPolicy = @{
Identity = 'Default'
NotifyOutboundSpamRecipients = $AdminMail
NotifyOutboundSpam = $true
ErrorAction = $STP
WarningAction = $SCT
}
$null = (Set-HostedOutboundSpamFilterPolicy @paramSetHostedOutboundSpamFilterPolicy)
Write-Output -InputObject 'Hosted Outbound Spam Filter Policy was fixed'
}
else
{
Write-Output -InputObject 'Hosted Outbound Spam Filter Policy was not fixed'
}
}
catch
{
Write-Warning -Message 'Unable to fix the Hosted Outbound Spam Filter Policy'
}
finally
{
$ExistingHostedOutboundSpamFilterPolicy = $null
$paramSetHostedOutboundSpamFilterPolicy = $null
}
#endregion
#region
# Deploying minimum baseline MobileDeviceMailboxPolicy
$paramGetMobileDeviceMailboxPolicy = @{
Identity = 'Default'
ErrorAction = $SCT
WarningAction = $SCT
}
$DefaultMobileDeviceMailboxPolicy = (Get-MobileDeviceMailboxPolicy @paramGetMobileDeviceMailboxPolicy | Select-Object -Property PasswordEnabled, AllowSimplePassword, AlphanumericPasswordRequired, MinPasswordLength, RequireDeviceEncryption, AllowNonProvisionableDevices)
#endregion
#region
# Check existing settings
if (($DefaultMobileDeviceMailboxPolicy.PasswordEnabled -eq $true) -and (($DefaultMobileDeviceMailboxPolicy.AllowSimplePassword -eq $true) -or ($DefaultMobileDeviceMailboxPolicy.AlphanumericPasswordRequired -eq $true)) -and ($DefaultMobileDeviceMailboxPolicy.MinPasswordLength -ge 4) -and ($DefaultMobileDeviceMailboxPolicy.RequireDeviceEncryption -eq $true) -and ($DefaultMobileDeviceMailboxPolicy.AllowNonProvisionableDevices -eq $false))
{
Write-Output -InputObject 'Minimum, or better, baseline MobileDeviceMailboxPolicy already applied'
}
else
{
try
{
$paramSetMobileDeviceMailboxPolicy = @{
Identity = 'Default'
PasswordEnabled = $true
AllowSimplePassword = $true
MinPasswordLength = 4
RequireDeviceEncryption = $true
AllowNonProvisionableDevices = $false
ErrorAction = $STP
WarningAction = $SCT
}
$null = (Set-MobileDeviceMailboxPolicy @paramSetMobileDeviceMailboxPolicy)
Write-Output -InputObject 'Minimum baseline MobileDeviceMailboxPolicy applied'
}
catch
{
Write-Warning -Message 'Unable to change and/or apply the Minimum baseline MobileDeviceMailboxPolicy'
}
}
$paramGetMobileDeviceMailboxPolicy = $null
$DefaultMobileDeviceMailboxPolicy = $null
$paramSetMobileDeviceMailboxPolicy = $null
#endregion
#region
# Enable general Modern Authentication
<#
PLEASE NOTE:
Microsoft Teams Rooms does NOT support Modern Authentication yet! (At least not with version 4.3.42.0 from 03/02/2019)
If you have a device like the Microsoft Teams Rooms, you might not be able to disable legacy Auth (basic authentication).
This will break the function and your device will no longer be able to authenticate.
Please check for the latest release notes:
https://docs.microsoft.com/en-us/MicrosoftTeams/rooms/rooms-release-note
#>
$paramGetOrganizationConfig = @{
ErrorAction = $SCT
WarningAction = $SCT
}
if (((Get-OrganizationConfig @paramGetOrganizationConfig) | Select-Object -ExpandProperty OAuth2ClientProfileEnabled) -ne $true)
{
try
{
$paramSetOrganizationConfig = @{
OAuth2ClientProfileEnabled = $true
ErrorAction = $STP
WarningAction = $SCT
}
$null = (Set-OrganizationConfig @paramSetOrganizationConfig)
Write-Output -InputObject 'Modern Authentication is enabled'
}
catch
{
Write-Warning -Message 'Unable to enable Modern Authentication'
}
}
else
{
Write-Output -InputObject 'Modern Authentication is already enabled'
}
$paramGetOrganizationConfig = $null
$paramSetOrganizationConfig = $null
#endregion
#region
# Enable Modern Authentication in Skype for Business Online
$paramGetCsOAuthConfiguration = @{
ErrorAction = $SCT
WarningAction = $SCT
}
if (((Get-CsOAuthConfiguration @paramGetCsOAuthConfiguration) | Select-Object -ExpandProperty ClientAdalAuthOverride) -ne 'Allowed')
{
try
{
$paramSetCsOAuthConfiguration = @{
ClientAdalAuthOverride = 'Allowed'
ErrorAction = $STP
WarningAction = $SCT
}
$null = (Set-CsOAuthConfiguration @paramSetCsOAuthConfiguration)
Write-Output -InputObject 'Modern Authentication was enabled for Skype for Business Online'
}
catch
{
Write-Warning -Message 'Unable to enable Modern Authentication for Skype for Business Online'
}
}
else
{
Write-Output -InputObject 'Modern Authentication is already enabled for Skype for Business Online'
}
$paramGetCsOAuthConfiguration = $null
$paramSetCsOAuthConfiguration = $null
#endregion
#region
# Block forwarding mail externally
$BlockForwardingRuleName = 'Block forwarding mail externally'
$paramGetTransportRule = @{
ErrorAction = $SCT
WarningAction = $SCT
}
if (-not (Get-TransportRule @paramGetTransportRule | Where-Object -FilterScript {
$_.Name -eq $BlockForwardingRuleName
}))
{
try
{
$paramNewTransportRule = @{
Name = $BlockForwardingRuleName
Priority = 1
SentToScope = 'NotInOrganization'
FromScope = 'InOrganization'
SenderAddressLocation = 'HeaderOrEnvelope'
MessageTypeMatches = 'AutoForward'
RejectMessageEnhancedStatusCode = '5.7.1'
RejectMessageReasonText = ('To improve security, auto-forwarding rules to external addresses has been disabled. Please contact ' + $AdminMail + " if you'd like to set up an exception.")
Mode = 'Audit'
Comments = 'Block forwarding mail externally'
ErrorAction = $STP
WarningAction = $SCT
}
$null = (New-TransportRule @paramNewTransportRule)
Write-Output -InputObject 'Block auto-forwarding rules to external addresses was created'
}
catch
{
Write-Warning -Message 'Block auto-forwarding rules to external addresses was not created'
}
}
else
{
Write-Output -InputObject 'Block auto-forwarding rules to external addresses already exists'
}
$BlockForwardingRuleName = $null
$paramGetTransportRule = $null
$paramNewTransportRule = $null
#endregion
#region
# Warn users if inbound external mail with display name matching internal users
$ExternalSenderWithInternalDisplayNameRuleName = 'External Senders with matching Display Names'
$paramGetTransportRule = @{
ErrorAction = $SCT
WarningAction = $SCT
}
if (-not (Get-TransportRule @paramGetTransportRule | Where-Object -FilterScript {
$_.Name -eq $ExternalSenderWithInternalDisplayNameRuleName
}))
{
try
{
# Please review! This text will be displayed to your users!!!
$ApplyHtmlDisclaimerText = "<table class=MsoNormalTable border=0 cellspacing=0 cellpadding=0 align=left width='100%' style='width:100.0%;mso-cellspacing:0cm;mso-yfti-tbllook:1184; mso-table-lspace:2.25pt;mso-table-rspace:2.25pt;mso-table-anchor-vertical:paragraph;mso-table-anchor-horizontal:column;mso-table-left:left;mso-padding-alt:0cm 0cm 0cm 0cm'> <tr style='mso-yfti-irow:0;mso-yfti-firstrow:yes;mso-yfti-lastrow:yes'><td style='background:red;padding:5.25pt 1.5pt 5.25pt 1.5pt'></td><td width='100%' style='width:100.0%;background:#ffe4e1;padding:5.25pt 3.75pt 5.25pt 11.25pt; word-wrap:break-word' cellpadding='7px 5px 7px 15px' color='#212121'><div><p class=MsoNormal style='mso-element:frame;mso-element-frame-hspace:2.25pt; mso-element-wrap:around;mso-element-anchor-vertical:paragraph;mso-element-anchor-horizontal: column;mso-height-rule:exactly'><span style='font-size:9.0pt;font-family: 'Segoe UI',sans-serif;mso-fareast-font-family:'Times New Roman';color:#212121'><strong>CAUTION:</strong> This email originated from <strong>outside</strong> of the organization by someone with a display name matching a user in your organization. Please do not click links or open attachments unless you recognize the source of this email and know the content is safe.<o:p></o:p></span></p></div></td></tr></table><p>&nbsp;</p>"
$paramNewTransportRule = @{
Name = $ExternalSenderWithInternalDisplayNameRuleName
Priority = 2
FromScope = 'NotInOrganization'
SenderAddressLocation = 'HeaderOrEnvelope'
ApplyHtmlDisclaimerLocation = 'Prepend'
HeaderMatchesMessageHeader = 'From'
HeaderMatchesPatterns = ((Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox, SharedMailbox) | Select-Object -ExpandProperty DisplayName)
ApplyHtmlDisclaimerText = $ApplyHtmlDisclaimerText
ApplyHtmlDisclaimerFallbackAction = 'Wrap'
SetHeaderName = 'X-bdcRule'
SetHeaderValue = $ExternalSenderWithInternalDisplayNameRuleName
ErrorAction = $STP
WarningAction = $SCT
}
$null = (New-TransportRule @paramNewTransportRule)
Write-Output -InputObject 'External Sender with internal Display Name Rule was created'
}
catch
{
Write-Warning -Message 'External Sender with internal Display Name Rule was not created'
}
}
else
{
Write-Output -InputObject 'External Sender with internal Display Name Rule already exists'
}
$ExternalSenderWithInternalDisplayNameRuleName = $null
$ApplyHtmlDisclaimerText = $null
$paramGetTransportRule = $null
$paramNewTransportRule = $null
#endregion
#region
# Mark all external Messages
$MarkExternalMessagesRuleName = 'Mark external Messages'
$paramGetTransportRule = @{
ErrorAction = $SCT
WarningAction = $SCT
}
if (-not (Get-TransportRule @paramGetTransportRule | Where-Object -FilterScript {
$_.Name -eq $MarkExternalMessagesRuleName
}))
{
try
{
# Please review! This text will be displayed to your users!!!
$ApplyHtmlDisclaimerText = "<table class=MsoNormalTable border=0 cellspacing=0 cellpadding=0 align=left width='100%' style='width:100.0%;mso-cellspacing:0cm;mso-yfti-tbllook:1184; mso-table-lspace:2.25pt;mso-table-rspace:2.25pt;mso-table-anchor-vertical:paragraph;mso-table-anchor-horizontal:column;mso-table-left:left;mso-padding-alt:0cm 0cm 0cm 0cm'> <tr style='mso-yfti-irow:0;mso-yfti-firstrow:yes;mso-yfti-lastrow:yes'><td style='background:yellow;padding:5.25pt 1.5pt 5.25pt 1.5pt'></td><td width='100%' style='width:100.0%;background:#ffffe0;padding:5.25pt 3.75pt 5.25pt 11.25pt; word-wrap:break-word' cellpadding='7px 5px 7px 15px' color='#212121'><div><p class=MsoNormal style='mso-element:frame;mso-element-frame-hspace:2.25pt; mso-element-wrap:around;mso-element-anchor-vertical:paragraph;mso-element-anchor-horizontal: column;mso-height-rule:exactly'><span style='font-size:9.0pt;font-family: 'Segoe UI',sans-serif;mso-fareast-font-family:'Times New Roman';color:#212121'><strong>CAUTION:</strong> This email originated from <strong>outside</strong> of the organization. Please do not click links or open attachments unless you recognize the source of this email and know the content is safe.<o:p></o:p></span></p></div></td></tr></table><p>&nbsp;</p>"
$paramNewTransportRule = @{
Name = $MarkExternalMessagesRuleName
Priority = 3
FromScope = 'NotInOrganization'
SenderAddressLocation = 'HeaderOrEnvelope'
ApplyHtmlDisclaimerLocation = 'Prepend'
ApplyHtmlDisclaimerText = $ApplyHtmlDisclaimerText
ApplyHtmlDisclaimerFallbackAction = 'Wrap'
SetHeaderName = 'X-bdcRule'
SetHeaderValue = $MarkExternalMessagesRuleName
ErrorAction = $STP
WarningAction = $SCT
}
$null = (New-TransportRule @paramNewTransportRule)
Write-Output -InputObject 'Mark External Messages Rule was created'
}
catch
{
Write-Warning -Message 'Mark External Messages Rule was not created'
}
}
else
{
Write-Output -InputObject 'Mark External Messages Rule already exists'
}
$MarkExternalMessagesRuleName = $null
$ApplyHtmlDisclaimerText = $null
$paramGetTransportRule = $null
$paramNewTransportRule = $null
#endregion
#region
# See the Description field, that will explain what each alter will do.
try
{
$paramNewActivityAlert = @{
Name = 'File and Page Alert'
Operation = 'Filemalwaredetected'
NotifyUser = $AdminMail
UserId = $null
Description = 'SharePoint anti-virus engine detects malware in a file.'
ErrorAction = $STP
WarningAction = $SCT
Severity = 'High'
EmailCulture = $EmailCulture
Disabled = $false
}
if (-not (Get-ActivityAlert -Name $paramNewActivityAlert.Name -ErrorAction $SCT))
{
$null = (New-ActivityAlert @paramNewActivityAlert)
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was created')
}
else
{
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert exists')
}
}
catch
{
Write-Warning -Message ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was not')
}
$paramNewActivityAlert = $null
try
{
$paramNewActivityAlert = @{
Name = 'Anonymous Links Alert'
Operation = 'Anonymouslinkcreated', 'Anonymouslinkupdated', 'Anonymouslinkused'
NotifyUser = $AdminMail
UserId = $null
Description = 'User created an anonymous link to a resource. User updated an anonymous link to a resource. An anonymous user accessed a resource by using an anonymous link.'
ErrorAction = $STP
WarningAction = $SCT
Severity = 'Medium'
EmailCulture = $EmailCulture
Disabled = $false
}
if (-not (Get-ActivityAlert -Name $paramNewActivityAlert.Name -ErrorAction $SCT))
{
$null = (New-ActivityAlert @paramNewActivityAlert)
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was created')
}
else
{
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert exists')
}
}
catch
{
Write-Warning -Message ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was not created')
}
$paramNewActivityAlert = $null
try
{
$paramNewActivityAlert = @{
Name = 'Sharing Alert'
Operation = 'Sharinginvitationcreated', 'Sharingpolicychanged'
NotifyUser = $AdminMail
UserId = $null
Description = "User shared a resource in SharePoint Online or OneDrive for Business with a user who isn't in your organization's directory. A SharePoint or global administrator changed a SharePoint sharing policy."
ErrorAction = $STP
WarningAction = $SCT
Severity = 'None'
EmailCulture = $EmailCulture
Disabled = $false
}
if (-not (Get-ActivityAlert -Name $paramNewActivityAlert.Name -ErrorAction $SCT))
{
$null = (New-ActivityAlert @paramNewActivityAlert)
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was created')
}
else
{
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert exists')
}
}
catch
{
Write-Warning -Message ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was not created')
}
$paramNewActivityAlert = $null
try
{
$paramNewActivityAlert = @{
Name = 'Access Alert'
Operation = 'Deviceaccesspolicychanged', 'Networkaccesspolicychanged'
NotifyUser = $AdminMail
UserId = $null
Description = 'Change in the unmanaged devices policy.Change in the location-based access policy (also called a trusted network boundary).'
ErrorAction = $STP
WarningAction = $SCT
Severity = 'Medium'
EmailCulture = $EmailCulture
Disabled = $false
}
if (-not (Get-ActivityAlert -Name $paramNewActivityAlert.Name -ErrorAction $SCT))
{
$null = (New-ActivityAlert @paramNewActivityAlert)
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was created')
}
else
{
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert exists')
}
}
catch
{
Write-Warning -Message ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was not created')
}
$paramNewActivityAlert = $null
try
{
$paramNewActivityAlert = @{
Name = 'Site Alert'
Operation = 'Sitecollectioncreated', 'Sitedeleted', 'Sitecollectionadminadded'
NotifyUser = $AdminMail
UserId = $null
Description = 'Creation of a new site collection OneDrive for Business site provisioned. A site was deleted.Site collection administrator or owner adds a person as a site collection administrator for a site.'
ErrorAction = $STP
WarningAction = $SCT
Severity = 'None'
EmailCulture = $EmailCulture
Disabled = $false
}
if (-not (Get-ActivityAlert -Name $paramNewActivityAlert.Name -ErrorAction $SCT))
{
$null = (New-ActivityAlert @paramNewActivityAlert)
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was created')
}
else
{
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert exists')
}
}
catch
{
Write-Warning -Message ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was not created')
}
$paramNewActivityAlert = $null
try
{
$paramNewActivityAlert = @{
Name = 'Office Alert'
Operation = 'Officeondemandset'
NotifyUser = $AdminMail
UserId = $null
Description = 'Site administrator enables Office on Demand, which lets users access the latest version of Office desktop applications.'
ErrorAction = $STP
WarningAction = $SCT
Severity = 'None'
EmailCulture = $EmailCulture
Disabled = $false
}
if (-not (Get-ActivityAlert -Name $paramNewActivityAlert.Name -ErrorAction $SCT))
{
$null = (New-ActivityAlert @paramNewActivityAlert)
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was created')
}
else
{
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert exists')
}
}
catch
{
Write-Warning -Message ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was not created')
}
$paramNewActivityAlert = $null
try
{
$paramNewActivityAlert = @{
Name = 'Mailbox Alert'
Operation = 'Add-mailboxpermission', 'Remove-mailboxpermission'
NotifyUser = $AdminMail
UserId = $null
Description = "An administrator assigned/removed the FullAccess mailbox permission to a user (known as a delegate) to another person`'s mailbox"
ErrorAction = $STP
WarningAction = $SCT
Severity = 'None'
EmailCulture = $EmailCulture
Disabled = $false
}
if (-not (Get-ActivityAlert -Name $paramNewActivityAlert.Name -ErrorAction $SCT))
{
$null = (New-ActivityAlert @paramNewActivityAlert)
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was created')
}
else
{
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert exists')
}
}
catch
{
Write-Warning -Message ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was not created')
}
$paramNewActivityAlert = $null
try
{
$paramNewActivityAlert = @{
Name = 'Password Alert'
Operation = 'Change user password.', 'Reset user password.', 'Set force change user password.'
NotifyUser = $AdminMail
UserId = $null
Description = 'User password changes'
ErrorAction = $STP
WarningAction = $SCT
Severity = 'None'
EmailCulture = $EmailCulture
Disabled = $false
}
if (-not (Get-ActivityAlert -Name $paramNewActivityAlert.Name -ErrorAction $SCT))
{
$null = (New-ActivityAlert @paramNewActivityAlert)
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was created')
}
else
{
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert exists')
}
}
catch
{
Write-Warning -Message ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was not created')
}
$paramNewActivityAlert = $null
try
{
$paramNewActivityAlert = @{
Name = 'Role Alert'
Operation = 'Add member to role.', 'Remove member from role.'
NotifyUser = $AdminMail
UserId = $null
Description = 'Added/Removed a user to an admin role in Office 365.'
ErrorAction = $STP
WarningAction = $SCT
Severity = 'Medium'
EmailCulture = $EmailCulture
Disabled = $false
}
if (-not (Get-ActivityAlert -Name $paramNewActivityAlert.Name -ErrorAction $SCT))
{
$null = (New-ActivityAlert @paramNewActivityAlert)
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was created')
}
else
{
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert exists')
}
}
catch
{
Write-Warning -Message ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was not created')
}
$paramNewActivityAlert = $null
try
{
$paramNewActivityAlert = @{
Name = 'Company Information Alert'
Operation = 'Set company contact information.', 'Set company information.', 'Set password policy.', 'Remove partner from company.'
NotifyUser = $AdminMail
UserId = $null
Description = 'Change company information or password policy'
ErrorAction = $STP
WarningAction = $SCT
Severity = 'High'
EmailCulture = $EmailCulture
Disabled = $false
}
if (-not (Get-ActivityAlert -Name $paramNewActivityAlert.Name -ErrorAction $SCT))
{
$null = (New-ActivityAlert @paramNewActivityAlert)
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was created')
}
else
{
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert exists')
}
}
catch
{
Write-Warning -Message ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was not created')
}
$paramNewActivityAlert = $null
try
{
$paramNewActivityAlert = @{
Name = 'Domain Alert'
Operation = 'Add domain to company.', 'Remove domain from company.', 'Update domain.'
NotifyUser = $AdminMail
UserId = $null
Description = 'Change of a custom domain in a tenant'
ErrorAction = $STP
WarningAction = $SCT
Severity = 'High'
EmailCulture = $EmailCulture
Disabled = $false
}
if (-not (Get-ActivityAlert -Name $paramNewActivityAlert.Name -ErrorAction $SCT))
{
$null = (New-ActivityAlert @paramNewActivityAlert)
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was created')
}
else
{
Write-Output -InputObject ('The ' + $paramNewActivityAlert.Name + ' Activity Alert exists')
}
}
catch
{
Write-Warning -Message ('The ' + $paramNewActivityAlert.Name + ' Activity Alert was not created')
}
$paramNewActivityAlert = $null
#endregion
}
#region LICENSE
<#
BSD 3-Clause License
Copyright (c) 2021, enabling Technology
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
#endregion LICENSE
#region DISCLAIMER
<#
DISCLAIMER:
- Use at your own risk, etc.
- This is open-source software, if you find an issue try to fix it yourself. There is no support and/or warranty in any kind
- This is a third-party Software
- The developer of this Software is NOT sponsored by or affiliated with Microsoft Corp (MSFT) or any of its subsidiaries in any way
- The Software is not supported by Microsoft Corp (MSFT)
- By using the Software, you agree to the License, Terms, and any Conditions declared and described above
- If you disagree with any of the terms, and any conditions declared: Just delete it and build your own solution
#>
#endregion DISCLAIMER