Added Files
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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=.+&data=')
|
||||
{
|
||||
$OriginalURL = $Matches[$Matches.Count - 1]
|
||||
|
||||
# Does the object use & instead of & to provide the data string
|
||||
$OriginalURL = (($OriginalURL -Split '\?url=')[1] -Split '&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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
200
Powershell/PowerShell-collection/Office365/Get-MFAUserReport.ps1
Normal file
200
Powershell/PowerShell-collection/Office365/Get-MFAUserReport.ps1
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
29
Powershell/PowerShell-collection/Office365/LICENSE
Normal file
29
Powershell/PowerShell-collection/Office365/LICENSE
Normal 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.
|
||||
@@ -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!!!'
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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> </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> </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
|
||||
Reference in New Issue
Block a user