├── .gitignore ├── Entra Connect └── Start-FullPasswordSync.ps1 ├── Exchange Legacy └── README.md ├── Exchange Online ├── Add-CustomCalendarEvents │ ├── Add-CustomCalendarEvents.ps1 │ ├── Add-EntraIdAppRegistration.ps1 │ ├── LICENSE.md │ ├── README.md │ ├── events.json │ └── settings.xml ├── Disable-RemotePowerShell │ ├── Disable-RemotePowerShell.ps1 │ ├── LICENSE │ └── README.md ├── Move-MigrationUser │ ├── LICENSE.md │ ├── Move-MigrationUser.ps1 │ └── README.md ├── Send-MonitoringEmail │ ├── Add-EntraIdAppRegistration.ps1 │ ├── LICENSE.md │ ├── Monitoring Attachment.docx │ ├── Monitoring Attachment.pdf │ ├── README.md │ ├── Send-MonitoringEmail.ps1 │ └── settings.xml └── Test-DomainAvailability │ ├── LICENSE.md │ ├── README.md │ └── Test-DomainAvailability.ps1 ├── Exchange Server ├── Add-HybridSendConnector │ └── Add-HybridSendConnector.ps1 ├── Copy-ReceiveConnector │ ├── Copy-ReceiveConnector.ps1 │ ├── LICENSE.md │ └── README.md ├── Export-MessageQueue │ ├── Export-MessageQueue.ps1 │ ├── LICENSE.md │ └── README.md ├── Export-ModernPublicFolderStatistics │ ├── Export-ModernPublicFolderStatistics.ps1 │ ├── Export-PublicFolderStatisticsToExcel.ps1 │ └── styles.css ├── Export-PublicFolderPermissions │ ├── Export-PublicFolderPermissions.ps1 │ └── Report-PFPermissions.ps1 ├── Get-ExchangeADVersion │ └── Get-ExchangeADVersion.ps1 ├── Get-ExchangeEnvironmentReport │ ├── EnvironmentReport.css │ ├── Get-ExchangeEnvironmentReport.ps1 │ ├── LICENSE.md │ ├── README.md │ ├── Run-ExchangeEnvironmentReport.ps1 │ └── images │ │ └── screenshot.png ├── Get-ExchangeServerCertificateReport │ ├── Get-ExchangeCertificateReport.ps1 │ ├── README.md │ └── styles.css ├── Get-NewPublicFolders │ ├── Get-NewPublicFolders.ps1 │ ├── LICENSE.md │ ├── README.md │ └── styles.css ├── Get-PublicFolderCustomFormItems │ ├── Get-PublicFolderCustomFormItems.ps1 │ ├── LICENSE.md │ └── README.md ├── Get-RemoteSmtpServers │ ├── Get-RemoteSmtpServers.ps1 │ ├── Get-RemoteSmtpServersTls.ps1 │ ├── LICENSE.md │ └── README.md ├── Import-EdgeSubscription │ ├── Import-EdgeSubscription.ps1 │ └── README.md ├── New-RoomMailbox │ ├── LICENSE.md │ ├── New-RoomMailbox.ps1 │ ├── README.md │ ├── Run-NewRoomMailbox.ps1 │ └── settings.xml ├── New-TeamMailbox │ ├── Create-TeamMailbox.ps1 │ ├── LICENSE.md │ ├── New-TeamMailbox.ps1 │ ├── README.md │ └── settings.xml ├── Purge-LogFiles │ ├── LICENSE.md │ ├── Purge-LogFiles.ps1 │ ├── README.md │ └── Run-PurgeLogFiles.ps1 ├── README.md ├── Remove-ProxyAddress │ └── Remove-ProxyAddress.ps1 ├── Set-EdgeDNSSuffix │ └── Set-EdgeDNSSuffix.ps1 ├── Set-ExchangePreferredRegistrySettings │ ├── Disable-IPv6-Correctly.reg │ ├── RPC-TimeOut-PortScaling.reg │ ├── Set-ExchangePreferredRegistrySettings.ps1 │ └── Tcpip-KeepAliveTime.reg ├── Set-PublicFolderPermissions │ └── Set-PublicFolderPermissions.ps1 ├── Set-UserPicture │ ├── LICENSE.md │ ├── README.md │ ├── Set-UserPicture.ps1 │ └── UserStatus.xml ├── Set-VirtualDirectoryUrl │ ├── LICENSE │ ├── README.md │ └── Set-VirtualDirectoryUrl.ps1 ├── Start-MailboxImport │ ├── LICENSE.md │ ├── README.md │ └── Start-MailboxImport.ps1 └── Test-ExchangeServerHealth │ ├── LICENSE.md │ ├── README.md │ └── Test-ExchangeServerHealth.ps1 ├── LICENSE ├── Misc ├── Get-Diskspace │ ├── Get-Diskspace.ps1 │ ├── Get-Diskspace.ps1.txt │ ├── LICENSE.md │ ├── README.md │ ├── Screenshot-Email.PNG │ └── Screenshot-Shell.PNG ├── Get-SoftwareInventory │ ├── Get-SoftwareInventory.ps1 │ ├── Get-SoftwareInventory.txt │ ├── LICENSE.md │ ├── README.md │ ├── Run-GetSoftwareInventory.ps1 │ └── Run-GetSoftwareInventoryExchange.ps1 ├── New-TestUser │ ├── LICENSE.md │ ├── New-TestUser.ps1 │ ├── README.md │ └── TestUserNames.xlsx └── Set-PageFileSize │ └── Set-PageFileSize.ps1 ├── Network ├── Test-DNSRecords │ ├── Domains.txt │ └── Test-DNSRecords.ps1 └── Test-NetworkAdapters │ └── Test-NetworkAdapters.ps1 ├── README-IMAGES ├── Add-CustomCalendarEvents-OWA.png └── Add-CustomCalendarEvents-PowerShell.png ├── README.md └── Script Template └── PowerShell-Script-Template.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | customer*.* 2 | secrect*.* 3 | **/logs/* 4 | 5 | -------------------------------------------------------------------------------- /Entra Connect/Start-FullPasswordSync.ps1: -------------------------------------------------------------------------------- 1 | # Name of Entra Connect AD connect 2 | $adConnector = "varunagroup.de" 3 | 4 | # Name ENtra Connect Entra connector 5 | $aadConnector = "varunagroup.onmicrosoft.com - AAD" 6 | 7 | 8 | Import-Module adsync 9 | $c = Get-ADSyncConnector -Name $adConnector 10 | $p = New-Object Microsoft.IdentityManagement.PowerShell.ObjectModel.ConfigurationParameter "Microsoft.Synchronize.ForceFullPasswordSync", String, ConnectorGlobal, $null, $null, $null 11 | $p.Value = 1 12 | $c.GlobalParameters.Remove($p.Name) 13 | $c.GlobalParameters.Add($p) 14 | $c = Add-ADSyncConnector -Connector $c 15 | 16 | 17 | Set-ADSyncAADPasswordSyncConfiguration -SourceConnector $adConnector -TargetConnector $aadConnector -Enable $false 18 | Set-ADSyncAADPasswordSyncConfiguration -SourceConnector $adConnector -TargetConnector $aadConnector -Enable $true 19 | 20 | 21 | Get-Mailbox -ResultSize Unlimited | Get-MailboxFolderStatistics -IncludeAnalysis -FolderScope All | Where-Object {(($_.TopSubjectSize -Match "MB") -and ($_.TopSubjectSize -GE 50.0)) -or ($_.TopSubjectSize -Match "GB")} | Select-Object Identity, TopSubject, TopSubjectSize | Export-CSV -path "C:\report.csv" -notype -------------------------------------------------------------------------------- /Exchange Legacy/README.md: -------------------------------------------------------------------------------- 1 | # Exchange Server PowerShell Scripts 2 | 3 | This GitHub Repository contains most of my public PowerShell scripts for legacy Exchange Server versions (2010, 2007). 4 | 5 | Please refer to the table of content in the main [README](https://github.com/Apoc70/PowerShell-Scripts) file. 6 | 7 | ### Stay connected 8 | 9 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 10 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 11 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 12 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 13 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 14 | 15 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 16 | 17 | * Website: [https://granikos.eu](https://granikos.eu) 18 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 19 | -------------------------------------------------------------------------------- /Exchange Online/Add-CustomCalendarEvents/Add-EntraIdAppRegistration.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Add a custom app registration to Entra ID for Microsoft Graph access 4 | 5 | Thomas Stensitzki 6 | 7 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 8 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 9 | 10 | Version 1.0, 2024-06-27 11 | 12 | Based on the work of Andres Bohren 13 | https://blog.icewolf.ch/archive/2022/12/02/create-azure-ad-app-registration-with-microsoft-graph-powershell 14 | 15 | .NOTES 16 | Requirements 17 | - PowerShell 7.1+ 18 | - Account executing this script must be a role member of the Application Administrator or Global Administrator role 19 | 20 | Revision History 21 | -------------------------------------------------------------------------------- 22 | 1.0 Initial community release 23 | 24 | .PARAMETER AppName 25 | 26 | The display name of the application in Entra ID 27 | 28 | .PARAMETER AppSecretName 29 | 30 | The name of the client secret in Entra ID 31 | 32 | .PARAMETER AppOwnerEmailAddress 33 | 34 | The email address of the application owner 35 | 36 | #> 37 | [CmdletBinding()] 38 | param( 39 | [string]$AppName = 'CustomCalendarItems-Application', 40 | [string]$AppSecretName = 'AppClientSecret', 41 | [string]$AppOwnerEmailAddress = 'Admin@TENANT.onmicrosoft.com' 42 | ) 43 | 44 | if ($null -ne (Get-Module -Name Microsoft.Graph.Authentication -ListAvailable).Version) { 45 | Import-Module -Name Microsoft.Graph.Authentication 46 | } 47 | else { 48 | Write-Warning -Message 'Unable to load Import-Module Microsoft.Graph.Authentication PowerShell module.' 49 | Write-Warning -Message 'Open an administrative PowerShell session and run Install-Module Microsoft.Graph' 50 | exit 51 | } 52 | if ($null -ne (Get-Module -Name Microsoft.Graph.Applications -ListAvailable).Version) { 53 | Import-Module -Name Microsoft.Graph.Applications 54 | } 55 | else { 56 | Write-Warning -Message 'Unable to load Import-Module Microsoft.Graph.Applications PowerShell module.' 57 | Write-Warning -Message 'Open an administrative PowerShell session and run Install-Module Microsoft.Graph' 58 | exit 59 | } 60 | 61 | # Connect to Microsoft Graph 62 | Connect-MgGraph -Scopes "Application.Read.All", "Application.ReadWrite.All", "User.Read.All" -NoWelcome 63 | 64 | # Create a new application 65 | $newApp= New-MgApplication -DisplayName $AppName -Notes 'Application for adding custom events to user calendars. This app is used by the Add-CustomCalendarItems.ps1 script.' 66 | $appObjectId = $newApp.Id 67 | 68 | # Set the owner of the application 69 | $User = Get-MgUser -UserId $AppOwnerEmailAddress 70 | $ObjectId = $User.ID 71 | $NewOwner = @{ 72 | "@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/{$ObjectId}" 73 | } 74 | $null = New-MgApplicationOwnerByRef -ApplicationId $appObjectId -BodyParameter $NewOwner 75 | 76 | # Create a new application client secret with a validity of 12 months 77 | $newAppSecret = @{ 78 | "displayName" = $AppSecretName 79 | "endDateTime" = (Get-Date).AddMonths(+12) 80 | } 81 | $appSecret = Add-MgApplicationPassword -ApplicationId $appObjectId -PasswordCredential $newAppSecret 82 | 83 | Write-Host 'Copy the following information to your settings file' -ForegroundColor Green 84 | Write-Host ('ClientSecret: {0}' -f $appSecret.SecretText) -ForegroundColor Green 85 | 86 | <# 87 | All permissions and IDs 88 | https://learn.microsoft.com/graph/permissions-reference#all-permissions-and-ids 89 | 90 | Calendars.ReadWrite Application ef54d2bf-783f-4e0f-bca1-3210c0444d99 91 | GroupMember.Read.All Application 98830695-27a2-44f7-8c18-0c3ebc9698f6 92 | User.ReadBasic.All Application 97235f07-e226-4f63-ace3-39588e11d3a1 93 | User.Read Delegated e1fe6dd8-ba31-4d61-89e7-88639da4683d 94 | #> 95 | 96 | $params = @{ 97 | RequiredResourceAccess = @( 98 | @{ 99 | ResourceAppId = "00000003-0000-0000-c000-000000000000" 100 | ResourceAccess = @( 101 | @{ 102 | Id = "98830695-27a2-44f7-8c18-0c3ebc9698f6" 103 | Type = "Role" 104 | }, 105 | @{ 106 | Id = "ef54d2bf-783f-4e0f-bca1-3210c0444d99" 107 | Type = "Role" 108 | }, 109 | @{ 110 | Id = "97235f07-e226-4f63-ace3-39588e11d3a1" 111 | Type = "Role" 112 | }, 113 | @{ 114 | Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d" 115 | Type = "Scope" 116 | } 117 | ) 118 | } 119 | ) 120 | } 121 | 122 | # Add permissions to the application 123 | $null = Update-MgApplication -ApplicationId $appObjectId -BodyParameter $params 124 | 125 | # Return the application ID for the settings file 126 | Write-Host ('ClientId (App ID): {0}' -f $newApp.AppId) -ForegroundColor Green 127 | 128 | # Set the application as a public client with a redirect URI 129 | $RedirectURI = @() 130 | $RedirectURI += "https://login.microsoftonline.com/common/oauth2/nativeclient" 131 | 132 | $params = @{ 133 | RedirectUris = @($RedirectURI) 134 | } 135 | 136 | $null = Update-MgApplication -ApplicationId $appObjectId -IsFallbackPublicClient -PublicClient $params 137 | 138 | # Open browser to grant admin consent 139 | $URL = ('https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/CallAnAPI/appId/{0}' -f $newApp.AppId) 140 | 141 | # Open the browser to grant admin consent 142 | # Wait for 30 seconds to allow Entra ID to provision the application 143 | Write-Host 'Browser will open in 30 seconds.' 144 | Start-Sleep -Seconds 30 145 | Start-Process $URL -------------------------------------------------------------------------------- /Exchange Online/Add-CustomCalendarEvents/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Exchange Online/Add-CustomCalendarEvents/README.md: -------------------------------------------------------------------------------- 1 | # Add-CustomCalendarItems.ps1 2 | 3 | Add calendar items to the default calendar of users in a security group 4 | 5 | ## Description 6 | 7 | The script reads a JSON file with event data and creates calendar items in the default calendar of users in a security group. 8 | 9 | Use the Add-EntraIdAppRegistration.ps1 script to create a custom application registration in Entra ID. 10 | 11 | Adjust the settings in the Settings.xml file to match your environment. 12 | 13 | ### Screenshots 14 | 15 | #### PowerShell 16 | 17 | Simple to run PowerShell 18 | 19 | ![PowerShell Example](https://github.com/Apoc70/PowerShell-Scripts/blob/main/README-IMAGES/Add-CustomCalendarEvents-PowerShell.png) 20 | 21 | #### Calendar 22 | 23 | This screenshot shows the result for a single event in Outlook on the Web 24 | 25 | ![Result in OWA calendar](https://github.com/Apoc70/PowerShell-Scripts/blob/main/README-IMAGES/Add-CustomCalendarEvents-OWA.png) 26 | 27 | ## Requirements 28 | 29 | - PowerShell 7.1+ 30 | - GlobalFunctions PowerShell module 31 | - Registered Entra ID application with access to Microsoft Graph 32 | 33 | ## Parameters 34 | 35 | ### EventFileName 36 | 37 | The name of the JSON file containing the event data located in the script directory. 38 | 39 | ### SettingsFileName 40 | 41 | The file name of the settings file located in the script directory. 42 | 43 | ## Example 44 | 45 | ``` PowerShell 46 | .\Add-CustomCalendarItems.ps1 -EventFileName CustomEvents.json -SettingsFileName CustomSettings.xml 47 | ``` 48 | 49 | Create calendar items for users based on the JSON file CustomEvents.json and the settings file CustomSettings.xml 50 | 51 | ## Note 52 | 53 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 54 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 55 | 56 | ## Credits 57 | 58 | Written by: Thomas Stensitzki 59 | 60 | The supporting script Add-EntraIdAppRegistration is based on content published by [Andres Bohren](https://blog.icewolf.ch/archive/2022/12/02/create-azure-ad-app-registration-with-microsoft-graph-powershell) 61 | 62 | ### Stay connected 63 | 64 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 65 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 66 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 67 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 68 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 69 | 70 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 71 | 72 | * Website: [https://granikos.eu](https://granikos.eu) 73 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 74 | -------------------------------------------------------------------------------- /Exchange Online/Add-CustomCalendarEvents/events.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "eventAction": "create", 4 | "eventData": { 5 | "subject": "Betriebsruhe", 6 | "location": "Betriebsruhe", 7 | "start": { 8 | "dateTime" : "2024-01-01T00:00:00", 9 | "timeZone" : "W. Europe Standard Time" 10 | }, 11 | "end": { 12 | "dateTime" : "2024-01-02T00:00:00", 13 | "timeZone" : "W. Europe Standard Time" 14 | }, 15 | "isAllDay": true, 16 | "showAs": "oof", 17 | "isreminderOn": false 18 | } 19 | }, 20 | { 21 | "eventAction": "create", 22 | "eventData": { 23 | "subject": "Betriebsruhe", 24 | "location": "Betriebsruhe", 25 | "start": { 26 | "dateTime" : "2024-10-04T00:00:00", 27 | "timeZone" : "W. Europe Standard Time" 28 | }, 29 | "end": { 30 | "dateTime" : "2024-10-05T00:00:00", 31 | "timeZone" : "W. Europe Standard Time" 32 | }, 33 | "isAllDay": true, 34 | "showAs": "oof", 35 | "isreminderOn": false 36 | } 37 | }, 38 | { 39 | "eventAction": "create", 40 | "eventData": { 41 | "subject": "Betriebsruhe", 42 | "location": "Betriebsruhe", 43 | "start": { 44 | "dateTime" : "2024-12-24T00:00:00", 45 | "timeZone" : "W. Europe Standard Time" 46 | }, 47 | "end": { 48 | "dateTime" : "2025-01-01T00:00:00", 49 | "timeZone" : "W. Europe Standard Time" 50 | }, 51 | "isAllDay": true, 52 | "showAs": "oof", 53 | "isreminderOn": false 54 | } 55 | } 56 | ] -------------------------------------------------------------------------------- /Exchange Online/Add-CustomCalendarEvents/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | TenantId 4 | ClientId 5 | ClientSecret 6 | SecurityGroupObjectId 7 | 8 | -------------------------------------------------------------------------------- /Exchange Online/Disable-RemotePowerShell/Disable-RemotePowerShell.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | Disable Exchange Remote PowerShell for all users, except for members of a selected security group. 5 | 6 | Thomas Stensitzki 7 | 8 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 9 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 10 | 11 | Version 1.0, 2023-02-03 12 | 13 | Please post ideas, comments, and suggestions at GitHub. 14 | 15 | .LINK 16 | 17 | https://scripts.Granikos.eu 18 | 19 | .DESCRIPTION 20 | 21 | This script sets the user attribute RemotePowerShellEnabled to FALSE for all users. 22 | You can and should provide an Active Directory universal security group containing all user accounts 23 | that should be able to use Exchange Remote PowerShell, e.g., Admins, Service Account, etc. 24 | 25 | .NOTES 26 | 27 | Requirements 28 | - Windows Server 2016 or newer 29 | - Global function PowerShell library, found here: http://scripts.granikos.eu 30 | - AciveDirectory PowerShell module 31 | - Exchange 2013+ Management Shell 32 | 33 | Revision History 34 | -------------------------------------------------------------------------------- 35 | 1.0 Initial community release 36 | 37 | .PARAMETER DaysToKeep 38 | 39 | Number of days Exchange and IIS log files should be retained, default is 30 days 40 | 41 | .PARAMETER AllowRPSGroupName 42 | 43 | Name of the Active Directory security group containing all user accounts that must have Remote PowerShell enabled. 44 | Add all user accounts (administrators, users, service accounts) to that security group. 45 | 46 | .PARAMETER LogAllowedUsers 47 | 48 | Switch to write information about RPS allowed users to the log file. 49 | 50 | .PARAMETER LogDisabledUsers 51 | 52 | Switch to write information about users RBS disabled to the log file. 53 | 54 | .EXAMPLE 55 | 56 | Disable Exchange Remote PowerShell for all users using default settings, and do not write any user details to a log file. 57 | 58 | .\Disable-RemotePowerShell 59 | 60 | #> 61 | 62 | 63 | [CmdletBinding()] 64 | Param( 65 | [int]$DaysToKeep = 30, 66 | [string]$AllowRPSGroupName = 'AllowRemotePS', 67 | [switch]$LogAllowedUsers, 68 | [switch]$LogDisabledUsers 69 | ) 70 | 71 | # Some error variables 72 | $ERR_OK = 0 73 | $ERR_GLOBALFUNCTIONSMISSING = 1098 74 | $ERR_NONELEVATEDMODE = 1099 75 | 76 | # Load Exchange Management Shell PowerShell Snap-In 77 | Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn 78 | 79 | # Import Active Directory Module 80 | Import-Module -Name ActiveDirectory 81 | 82 | # Import GlobalFunctions 83 | if($null -ne (Get-Module -Name GlobalFunctions -ListAvailable).Version) { 84 | Import-Module -Name GlobalFunctions 85 | } 86 | else { 87 | Write-Warning -Message 'Unable to load GlobalFunctions PowerShell module.' 88 | Write-Warning -Message 'Open an administrative PowerShell session and run Import-Module GlobalFunctions' 89 | Write-Warning -Message 'Please check http://bit.ly/GlobalFunctions for further instructions' 90 | exit $ERR_GLOBALFUNCTIONSMISSING 91 | } 92 | 93 | $ScriptDir = Split-Path -Path $script:MyInvocation.MyCommand.Path 94 | $ScriptName = $MyInvocation.MyCommand.Name 95 | $logger = New-Logger -ScriptRoot $ScriptDir -ScriptName $ScriptName -LogFileRetention 14 96 | $logger.Write('Script started') 97 | 98 | # Check if we are running in elevated mode 99 | # function (c) by Michel de Rooij 100 | function script:Test-IsAdmin { 101 | $currentPrincipal = New-Object -TypeName Security.Principal.WindowsPrincipal -ArgumentList ( [Security.Principal.WindowsIdentity]::GetCurrent() ) 102 | 103 | If( $currentPrincipal.IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator )) { 104 | return $true 105 | } 106 | else { 107 | return $false 108 | } 109 | } 110 | 111 | If (Test-IsAdmin) { 112 | # We are running in elevated mode. Let's continue. 113 | 114 | $logger.Write(('{1} started, keeping last {0} days of log files.' -f $DaysToKeep, $ScriptName)) 115 | $logger.Purge() 116 | 117 | # Get all users with enabled Remote PowerShell 118 | $AllRPSUsers = Get-User -ResultSize Unlimited -Filter 'RemotePowerShellEnabled -eq $true' | select SamAccountName, RemotePowerShellEnabled 119 | 120 | # log number of users with RemotePowerShellEnabled enabled 121 | $logger.Write( ('Users with RemotePowerShellEnabled: {0}' -f ($AllRPSUsers | Measure-Object).Count )) 122 | 123 | # Get all users from AllowRPSGroupName 124 | 125 | $AllowedRPSUsers = Get-ADGroupMember -Identity $AllowRPSGroupName -Recursive | ForEach-Object { Get-User -Identity $_.SamAccountName | Select-Object SamAccountName, RemotePowerShellEnabled } 126 | 127 | # log number of users that are allowed to have RemotePowerShellEnabled 128 | $logger.Write( ('Number of allowed users: {0}' -f ($AllowedRPSUsers | Measure-Object).Count )) 129 | 130 | # Enable Remote PowerShell for allowed users 131 | foreach ($AllowedUser in $AllowedRPSUsers) { 132 | if ($AllowedUser.RemotePowerShellEnabled -eq $false) { 133 | if($LogAllowedUsers) { 134 | # write user information to log file 135 | $logger.Write( ('Setting RemotePowerShellEnabled to TRUE for: {0}' -f $AllowedUser.SamAccountName )) 136 | } 137 | 138 | # Set RemotePowerShellEnabled to TRUE for the user 139 | Set-User $AllowedUser.SamAccountName -RemotePowerShellEnabled $true 140 | } 141 | } 142 | 143 | # Disable Remote PowerShell for all users 144 | foreach ($User in $AllRPSUsers) { 145 | 146 | if ($AllowedRPSUsers.SamAccountName -notcontains $User.SamAccountName) { 147 | if($LogDisabledUsers) { 148 | # write user information to log file 149 | $logger.Write( ('Setting RemotePowerShellEnabled to FALSE for: {0}' -f $User.SamAccountName )) 150 | } 151 | 152 | # Set RemotePowerShellEnabled to FALSE for the user 153 | Set-User $User.SamAccountName -RemotePowerShellEnabled $false 154 | 155 | } 156 | } 157 | 158 | $logger.Write('Script finished') 159 | 160 | Return $ERR_OK 161 | } 162 | else { 163 | # Ooops, the admin did it again. 164 | Write-Warning -Message 'You must run the script in elevated mode. Please start the PowerShell-Session with administrative privileges.' 165 | 166 | Return $ERR_NONELEVATEDMODE 167 | } -------------------------------------------------------------------------------- /Exchange Online/Disable-RemotePowerShell/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Exchange Online/Disable-RemotePowerShell/README.md: -------------------------------------------------------------------------------- 1 | # Disable-RemotePowerShell 2 | 3 | Disable Exchange Remote PowerShell for all users, except for members of a selected security group. 4 | 5 | ## Description 6 | 7 | This script disables the use of RemotePoweShell und EXOPowerSHell module for Microsoft 365 users, except for members of a given security group. 8 | 9 | ## Requirements 10 | 11 | - Exchange Online Management Shell v2 12 | 13 | ## Parameters 14 | 15 | ### DaysToKeep 16 | 17 | Number of days Exchange and IIS log files should be retained, default is 30 days 18 | 19 | ### AllowRPSGroupName 20 | 21 | Name of the Active Directory security group containing all user accounts that must have Remote PowerShell enabled. 22 | Add all user accounts (administrators, users, service accounts) to that security group. 23 | 24 | ### LogAllowedUsers 25 | 26 | Switch to write information about RPS allowed users to the log file. 27 | 28 | ### LogDisabledUsers 29 | 30 | Switch to write information about users RBS disabled to the log file. 31 | 32 | ## Example 33 | 34 | ``` PowerShell 35 | .\Disable-RemotePowerShell 36 | ``` 37 | 38 | Disable Exchange Remote PowerShell for all users using default settings, and do not write any user details to a log file. 39 | 40 | ## Note 41 | 42 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 43 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 44 | 45 | ### Stay connected 46 | 47 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 48 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 49 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 50 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 51 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 52 | 53 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 54 | 55 | * Website: [https://granikos.eu](https://granikos.eu) 56 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 57 | -------------------------------------------------------------------------------- /Exchange Online/Move-MigrationUser/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Exchange Online/Move-MigrationUser/Move-MigrationUser.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script creates a new migration batch and moves migration users from one batch to the new batch. 4 | 5 | Thomas Stensitzki 6 | 7 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 8 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 9 | 10 | Version 1.0, 2022-08-29 11 | 12 | Please use GitHub repository for ideas, comments, and suggestions. 13 | 14 | .LINK 15 | https://github.com/Apoc70/PowerShell-Scripts 16 | https://scripts.granikos.eu 17 | 18 | .DESCRIPTION 19 | 20 | This script moves Exchange Online migration users to a new migration batch. The script creates 21 | the new batch automatically. The name of the new batch is based on the following: 22 | * Custom prefix, provided by the BatchName parameter 23 | * Batch completion date 24 | * Source batch name 25 | 26 | The new batch name helps you to easily identify the planned completion date and source of the 27 | migration users. This is helpful during an agile mailbox migration process. 28 | 29 | .NOTES 30 | Requirements 31 | - Exchange Online Management Shell v2 32 | 33 | Revision History 34 | -------------------------------------------------------------------------------- 35 | 1.0 Initial community release 36 | 37 | .PARAMETER Users 38 | 39 | List of migration user email addresses that should move to a new migration batch 40 | 41 | .PARAMETER UsersCsvFile 42 | 43 | Path to a CSV file containing a migration users, one user migration email address per line 44 | 45 | .PARAMETER BatchName 46 | 47 | The name of the new migration batch. The BatchName is the first part of the final full Name, e.g. BATCHNAME_2022-08-17_SOURCEBATCHNAME 48 | 49 | .PARAMETER Autostart 50 | 51 | Switch indicating that the new migration batch should start automatically 52 | 53 | .PARAMETER AutoComplete 54 | 55 | Switch indicating that the new migration should complete migration automatically 56 | NOT implemented yet 57 | 58 | .PARAMETER CompleteDateTime 59 | 60 | [DateTime] defining the completion date and time for the new batch 61 | 62 | .PARAMETER NotificationEmails 63 | 64 | Email addresses for batch notification emails 65 | 66 | .PARAMETER DateTimePattern 67 | 68 | The string pattern used for date information in the batch name 69 | 70 | .EXAMPLE 71 | 72 | ./Move-MigrationUsers -Users JohnDoe@varunagroup.de,JaneDoe@varunagroup.de -CompleteDateTime '2022/08/31 18:00' 73 | 74 | Move two migration users from the their current migration batch to a new migration batch 75 | #> 76 | 77 | [CmdletBinding()] 78 | param( 79 | 80 | $Users = @( 'JohnDoe@varunagroup.de' ), 81 | # $UsersCsvFile = '', 82 | [string]$BatchName = 'BATCH', 83 | [switch]$Autostart, 84 | # [switch]$AutoComplete, 85 | [Parameter(Mandatory=$true)] 86 | [datetime]$CompleteDateTime, 87 | $NotificationEmails = 'VarunaAdmin@varunagroup.de', 88 | [string]$DateTimePattern = 'yyyy-MM-dd' 89 | ) 90 | 91 | if (($Users | Measure-Object).Count -ne 0) { 92 | 93 | # Create name for en migration batch 94 | $BatchNamePrefix = ('{0}_{1}' -f $BatchName, ($CompleteDateTime.ToString($DateTimePattern))) 95 | Write-Verbose -Message (('New target batch name: {0}' -f $BatchName)) 96 | 97 | # Fetching users 98 | Write-Verbose -Message ('Parsing {0} users' -f (($Users | Measure-Object).Count)) 99 | 100 | # Fetch migration users and group by BatchId 101 | # ToDo: Pre-Check users for existence as migration user and a non-completed migration status 102 | $MigrationsUsers = $Users | ForEach-Object { Get-MigrationUser -Identity $_ | Select-Object Identity, BatchId } | Group-Object BatchId 103 | 104 | # Parse migration users 105 | $MigrationsUsers | ForEach-Object { 106 | 107 | Write-Output ('{1} - {0}'-f $_.Name, $_.Group.Identity) 108 | 109 | # assemble new batch name 110 | # adjust to your personal needs 111 | $NewBatchName = ('{0}_{1}' -f $BatchNamePrefix, $_.Name) 112 | 113 | # Create new migration batch 114 | if($Autostart) { 115 | $Batch = New-MigrationBatch -Name $NewBatchName -UserIds $_.Group.Identity -DisableOnCopy -AutoStart -NotificationEmails $NotificationEmails 116 | } 117 | else { 118 | $Batch = New-MigrationBatch -Name $NewBatchName -UserIds $_.Group.Identity -DisableOnCopy -NotificationEmails $NotificationEmails 119 | } 120 | 121 | # Output detailed batch information 122 | $Batch | Format-List 123 | 124 | # Set auto completion date and time if not null 125 | # Set time as Universal Time 126 | if($null -ne $CompleteDateTime) { 127 | 128 | Write-Verbose ('Setting CompleteAfter to: {0}' -f ($CompleteTime).ToUniversalTime()) 129 | 130 | Set-MigrationBatch -Identity $NewBatchName -CompleteAfter ($CompleteTime).ToUniversalTime() 131 | } 132 | 133 | } 134 | } 135 | else { 136 | Write-Output 'The list of users is empty. Nothing to do today.' 137 | } 138 | -------------------------------------------------------------------------------- /Exchange Online/Move-MigrationUser/README.md: -------------------------------------------------------------------------------- 1 | # Move-MigrationUser.ps1 2 | 3 | This script creates a new migration batch and moves migration users from one batch to the new batch. 4 | 5 | ## Description 6 | 7 | This script moves Exchange Online migration users to a new migration batch. The script creates the new batch automatically. The name of the new batch is based on the following: 8 | 9 | - Custom prefix, provided by the BatchName parameter 10 | - Batch completion date 11 | - Source batch name 12 | 13 | The new batch name helps you to easily identify the planned completion date and source of the migration users. This is helpful during an agile mailbox migration process. 14 | 15 | ## Requirements 16 | 17 | - Exchange Online Management Shell v2 18 | 19 | ## Parameters 20 | 21 | ### Users 22 | 23 | List of migration user email addresses that should move to a new migration batch 24 | 25 | ### UsersCsvFile 26 | 27 | Path to a CSV file containing a migration users, one user migration email address per line 28 | 29 | ### BatchName 30 | 31 | The name of the new migration batch. The BatchName is the first part of the final full Name, e.g. BATCHNAME_2022-08-17_SOURCEBATCHNAME 32 | 33 | ### Autostart 34 | 35 | Switch indicating that the new migration batch should start automatically 36 | 37 | ### AutoComplete 38 | 39 | Switch indicating that the new migration should complete migration automatically 40 | NOT implemented yet 41 | 42 | ### CompleteDateTime 43 | 44 | [DateTime] defining the completion date and time for the new batch 45 | 46 | ### NotificationEmails 47 | 48 | Email addresses for batch notification emails 49 | 50 | ### DateTimePattern 51 | 52 | The string pattern used for date information in the batch name 53 | 54 | ## Example 55 | 56 | ``` PowerShell 57 | ./Move-MigrationUsers -Users JohnDoe@varunagroup.de,JaneDoe@varunagroup.de -CompleteDateTime '2022/08/31 18:00' 58 | ``` 59 | 60 | Move two migration users from the their current migration batch to a new migration batch 61 | 62 | ## Note 63 | 64 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 65 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 66 | 67 | ### Stay connected 68 | 69 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 70 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 71 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 72 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 73 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 74 | 75 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 76 | 77 | * Website: [https://granikos.eu](https://granikos.eu) 78 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 79 | -------------------------------------------------------------------------------- /Exchange Online/Send-MonitoringEmail/Add-EntraIdAppRegistration.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Add a custom app registration to Entra ID for Microsoft Graph access 4 | 5 | Thomas Stensitzki 6 | 7 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 8 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 9 | 10 | Version 1.0, 2024-07-22 11 | 12 | Based on the work of Andres Bohren 13 | https://blog.icewolf.ch/archive/2022/12/02/create-azure-ad-app-registration-with-microsoft-graph-powershell 14 | 15 | .NOTES 16 | Requirements 17 | - PowerShell 7.1+ 18 | - Account executing this script must be a role member of the Application Administrator or Global Administrator role 19 | 20 | Use Connect-MgGraph -Scopes "Directory.ReadWrite.All" -NoWelcome to connect to Microsoft Graph when creating the application. 21 | 22 | When using application permissions for Microsoft Grapg, consider restricting access to the application to specific users or groups: 23 | https://bit.ly/LimitExoAppAccess 24 | 25 | #> 26 | [CmdletBinding()] 27 | param( 28 | [string]$AppName = 'CustomSendEmail-Application', 29 | [string]$AppSecretName = 'AppClientSecret', 30 | # Update the email address to the email address of the user that should be the owner of the application 31 | [string]$AppOwnerEmailAddress = 'admin@egxde.onmicrosoft.com', 32 | [int]$AppSecretLifetime = 12 33 | ) 34 | 35 | # Load required modules 36 | 37 | if ($null -ne (Get-Module -Name Microsoft.Graph.Authentication -ListAvailable).Version) { 38 | # Import the Microsoft.Graph.Authentication module 39 | Import-Module -Name Microsoft.Graph.Authentication 40 | } 41 | else { 42 | # Display a warning and exit the script 43 | Write-Warning -Message 'Unable to load Import-Module Microsoft.Graph.Authentication PowerShell module.' 44 | Write-Warning -Message 'Open an administrative PowerShell session and run Install-Module Import-Module Microsoft.Graph' 45 | exit 46 | } 47 | if ($null -ne (Get-Module -Name Microsoft.Graph.Applications -ListAvailable).Version) { 48 | # Import the Microsoft.Graph.Applications module 49 | Import-Module -Name Microsoft.Graph.Applications 50 | } 51 | else { 52 | # Display a warning and exit the script 53 | Write-Warning -Message 'Unable to load Import-Module Microsoft.Graph.Applications PowerShell module.' 54 | Write-Warning -Message 'Open an administrative PowerShell session and run Install-Module Import-Module Microsoft.Graph' 55 | exit 56 | } 57 | 58 | # Connect to Microsoft Graph 59 | Connect-MgGraph -Scopes "Application.Read.All", "Application.ReadWrite.All", "User.Read.All" -NoWelcome 60 | 61 | # Create a new application 62 | $newApp= New-MgApplication -DisplayName $AppName -Notes 'Application for sending monitoring emails. This app is used by the Send-MonitoringEmail.ps1 script.' 63 | $appObjectId = $newApp.Id 64 | 65 | # Set the owner of the application 66 | $User = Get-MgUser -UserId $AppOwnerEmailAddress 67 | $ObjectId = $User.ID 68 | $NewOwner = @{ 69 | "@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/{$ObjectId}" 70 | } 71 | 72 | $null = New-MgApplicationOwnerByRef -ApplicationId $appObjectId -BodyParameter $NewOwner 73 | 74 | # Create a new application client secret with a validity of 12 months 75 | $newAppSecret = @{ 76 | "displayName" = $AppSecretName 77 | "endDateTime" = (Get-Date).AddMonths($AppSecretLifetime) 78 | } 79 | $appSecret = Add-MgApplicationPassword -ApplicationId $appObjectId -PasswordCredential $newAppSecret 80 | 81 | Write-Host 'Copy the following information to your settings file' -ForegroundColor Green 82 | Write-Host ('ClientSecret: {0}' -f $appSecret.SecretText) -ForegroundColor Green 83 | 84 | <# 85 | All permissions and IDs 86 | https://learn.microsoft.com/graph/permissions-reference#all-permissions-and-ids 87 | 88 | Mail.ReadWrite Application e2a3a72e-5f79-4c64-b1b1-878b674786c9 89 | Mail.Send Application b633e1c5-b582-4048-a93e-9f11b44c7e96 90 | User.Read Delegated e1fe6dd8-ba31-4d61-89e7-88639da4683d 91 | User.Read.All Application df021288-bdef-4463-88db-98f22de89214 92 | #> 93 | 94 | $params = @{ 95 | RequiredResourceAccess = @( 96 | @{ 97 | ResourceAppId = "00000003-0000-0000-c000-000000000000" 98 | ResourceAccess = @( 99 | @{ 100 | Id = "df021288-bdef-4463-88db-98f22de89214" 101 | Type = "Role" 102 | }, 103 | @{ 104 | Id = "e2a3a72e-5f79-4c64-b1b1-878b674786c9" 105 | Type = "Role" 106 | }, 107 | @{ 108 | Id = "b633e1c5-b582-4048-a93e-9f11b44c7e96" 109 | Type = "Role" 110 | }, 111 | @{ 112 | Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d" 113 | Type = "Scope" 114 | } 115 | ) 116 | } 117 | ) 118 | } 119 | 120 | # Add permissions to the application 121 | $null = Update-MgApplication -ApplicationId $appObjectId -BodyParameter $params 122 | 123 | # Return the application ID for the settings file 124 | Write-Host ('ClientId (App ID): {0}' -f $newApp.AppId) -ForegroundColor Green 125 | 126 | # Set the application as a public client with a redirect URI 127 | $RedirectURI = @() 128 | $RedirectURI += "https://login.microsoftonline.com/common/oauth2/nativeclient" 129 | 130 | $params = @{ 131 | RedirectUris = @($RedirectURI) 132 | } 133 | 134 | $null = Update-MgApplication -ApplicationId $appObjectId -IsFallbackPublicClient -PublicClient $params 135 | 136 | # Open browser to grant admin consent 137 | $URL = ('https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/CallAnAPI/appId/{0}' -f $newApp.AppId) 138 | 139 | # Open the browser to grant admin consent 140 | # Wait for 30 seconds to allow Entra ID to provision the application 141 | Write-Host 'Browser will open in 30 seconds.' 142 | Start-Sleep -Seconds 30 143 | Start-Process $URL -------------------------------------------------------------------------------- /Exchange Online/Send-MonitoringEmail/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Exchange Online/Send-MonitoringEmail/Monitoring Attachment.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apoc70/PowerShell-Scripts/ef43113b389502d309e246891fa9c7a3ad5a147e/Exchange Online/Send-MonitoringEmail/Monitoring Attachment.docx -------------------------------------------------------------------------------- /Exchange Online/Send-MonitoringEmail/Monitoring Attachment.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apoc70/PowerShell-Scripts/ef43113b389502d309e246891fa9c7a3ad5a147e/Exchange Online/Send-MonitoringEmail/Monitoring Attachment.pdf -------------------------------------------------------------------------------- /Exchange Online/Send-MonitoringEmail/README.md: -------------------------------------------------------------------------------- 1 | # Add-CustomCalendarItems.ps1 2 | 3 | Send a single monitoring email to a recipient 4 | 5 | ## Description 6 | 7 | This script send a single monitoring email to a recipient. The script is intended to be used as a monitoring script. 8 | The message body contains a GUID and the current date and time in ticks. 9 | 10 | Use the Add-EntraIdAppRegistration.ps1 script to create a custom application registration in Entra ID. 11 | 12 | Adjust the settings in the Settings.xml file to match your environment. 13 | 14 | When using application permissions for Microsoft Grapg, consider restricting access to the application to specific users or groups: 15 | [https://bit.ly/LimitExoAppAccess](https://bit.ly/LimitExoAppAccess) 16 | 17 | ## Requirements 18 | 19 | - PowerShell 7.1+ 20 | - GlobalFunctions PowerShell module 21 | - Registered Entra ID application with access to Microsoft Graph 22 | 23 | ## Parameters 24 | 25 | ### SettingsFileName 26 | 27 | The file name of the settings file located in the script directory. 28 | 29 | ## Example 30 | 31 | ``` PowerShell 32 | .\Add-CustomCalendarItems.ps1 -SettingsFileName CustomSettings.xml 33 | ``` 34 | 35 | Send a monitoring email using the selected settings file 36 | 37 | ## Note 38 | 39 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 40 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 41 | 42 | ## Credits 43 | 44 | Written by: Thomas Stensitzki 45 | 46 | THe supporting script Add-EntraIdAppRegistration is based on content published by [Andres Bohren](https://blog.icewolf.ch/archive/2022/12/02/create-azure-ad-app-registration-with-microsoft-graph-powershell) 47 | 48 | ### Stay connected 49 | 50 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 51 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 52 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 53 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 54 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 55 | 56 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 57 | 58 | * Website: [https://granikos.eu](https://granikos.eu) 59 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 60 | -------------------------------------------------------------------------------- /Exchange Online/Send-MonitoringEmail/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TenantId 5 | 6 | ClientId 7 | 8 | ClientSecret 9 | 10 | 11 | 12 | user@yourdomain.com 13 | 14 | recipient@somedomain.com 15 | 16 | Monitoring Email 17 | 18 | Text 19 | 20 | Monitoring Attachment.pdf 21 | 22 | false 23 | 24 | -------------------------------------------------------------------------------- /Exchange Online/Test-DomainAvailability/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Exchange Online/Test-DomainAvailability/README.md: -------------------------------------------------------------------------------- 1 | # Test-DomainAvailability.ps1 2 | 3 | ## Description 4 | 5 | The script queries the login uri for the selected Office 365 region. The response contains metadata about the domain queried. 6 | 7 | If the domain already exists in the specified region the metadata contains information if the domain is verified and/or federated 8 | 9 | Load function into your current PowerShell session: 10 | 11 | ``` PowerShell 12 | . .\Test-DomainAvailability.ps1 13 | ``` 14 | 15 | ## Parameters 16 | 17 | ### DomainName 18 | 19 | The domain name you want to verify. Example: example.com 20 | 21 | ### LookupRegion 22 | 23 | The Office 365 region where you want to verify the domain. 24 | Currently implemented: Global, Germany, China 25 | 26 | ## Examples 27 | 28 | ``` PowerShell 29 | Test-DomainAvailability -DomainName example.com 30 | ``` 31 | 32 | Test domain availability in the default region - Office 365 Global 33 | 34 | ``` PowerShell 35 | Test-DomainAvailability -DomainName example.com -LookupRegion China 36 | ``` 37 | 38 | Test domain availability in Office 365 China 39 | 40 | ## Note 41 | 42 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 43 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 44 | 45 | ## Credits 46 | 47 | Written by: Thomas Stensitzki 48 | 49 | ### Stay connected 50 | 51 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 52 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 53 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 54 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 55 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 56 | 57 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 58 | 59 | * Website: [https://granikos.eu](https://granikos.eu) 60 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 61 | -------------------------------------------------------------------------------- /Exchange Online/Test-DomainAvailability/Test-DomainAvailability.ps1: -------------------------------------------------------------------------------- 1 | function Test-DomainAvailability { 2 | <# 3 | .SYNOPSIS 4 | Check the availability of a domain in a selected Office 365 region. 5 | 6 | .DESCRIPTION 7 | The script queries the login uri for the selected Office 365 region. The response contains metadata about the domain queried. 8 | 9 | If the domain already exists in the specified region the metadata contains information if the domain is verified and/or federated 10 | 11 | Load function into your current PowerShell session: 12 | . .\Test-DomainAvailability.ps1 13 | 14 | .PARAMETER DomainName 15 | The domain name you want to verify. Example: example.com 16 | 17 | .PARAMETER LookupRegion 18 | The Office 365 region where you want to verify the domain. 19 | Currently implemented: Global, Germany, China 20 | 21 | .NOTES 22 | 23 | Author: ? 24 | (Source: https://blogs.technet.microsoft.com/tip_of_the_day/2017/02/16/cloud-tip-of-the-day-use-powershell-to-check-domain-availability-for-office-365-and-azure/) 25 | Enhancement: Thomas Stensitzki 26 | 27 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 28 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 29 | 30 | Version 1.1, 2024-08-20 31 | 32 | .LINK 33 | https://scripts.Granikos.eu 34 | 35 | .EXAMPLE 36 | Test-DomainAvailability -DomainName example.com 37 | 38 | Test domain availability in the default region - Office 365 Global 39 | 40 | .EXAMPLE 41 | Test-DomainAvailability -DomainName example.com -LookupRegion China 42 | 43 | Test domain availability in Office 365 China 44 | #> 45 | 46 | param( 47 | [Parameter(Mandatory=$true,HelpMessage='A domain name (e.g. example.com) is required')] 48 | [string]$DomainName, 49 | [ValidateSet('Global','Germany','China')] 50 | [string]$LookupRegion = 'Global' 51 | ) 52 | 53 | # Define descriptions for status codes 54 | $descriptions = @{ 55 | Unknown = 'Domain does not exist in Office 365/Azure AD' 56 | Managed = 'Domain is verified but not federated' 57 | Federated = 'Domain is verified and federated' 58 | } 59 | 60 | # Define login Uris for Office 365 regions, extend as needed and add hashtable key to param validate set 61 | $Regions = @{ 62 | Global = 'login.microsoftonline.com' 63 | Germany = 'login.microsoftonline.de' 64 | China = 'login.partner.microsoftonline.cn' 65 | } 66 | 67 | # Select Lookup uri 68 | $LookupUri = $Regions[$LookupRegion] 69 | 70 | if($LookupUri -ne '') { 71 | 72 | # 73 | $response = Invoke-WebRequest -Uri ('https://{0}/getuserrealm.srf?login=user@{1}&xml=1' -f $LookupUri, $DomainName) 74 | 75 | if($response -and $response.StatusCode -eq 200) { 76 | 77 | # Check namespace 78 | $namespaceType = ([xml]($response.Content)).RealmInfo.NameSpaceType 79 | 80 | New-Object -TypeName PSObject -Property @{ 81 | 82 | DomainName = $DomainName 83 | 84 | NamespaceType = $namespaceType 85 | 86 | Details = $descriptions[$namespaceType] 87 | 88 | } | Select-Object -Property DomainName, NamespaceType, Details 89 | 90 | } 91 | else { 92 | # We were not ablte to connect to lookup uri. Do wen have an Internet connection? 93 | 94 | Write-Error -Message 'Domain could not be verified. Please check your connectivity to login.microsoftonline.com' 95 | 96 | } 97 | 98 | } 99 | 100 | } -------------------------------------------------------------------------------- /Exchange Server/Add-HybridSendConnector/Add-HybridSendConnector.ps1: -------------------------------------------------------------------------------- 1 | # Send Connector Parameters 2 | 3 | # The name of the connector 4 | $sendConnectorName = "E-Mail to Internet via ExchangeOnline" 5 | 6 | # Use the same Fqdn as the one used for your hybrid send connector created by HCW 7 | $sendConnectorFqdn = "smtpo.varunagroup.de" 8 | 9 | # Source transpport server(s) for the send connector 10 | # Might be an Edge Transport Server 11 | $sendConnectorSourceTransportServers = "EX01" 12 | 13 | # Maximum message size for the send connector 14 | $sendConnectorMaxMessageSize = 100MB 15 | 16 | # Use the MX host provided in the Microsoft 365 Admin Center for your primary custom domain 17 | $sendConnectorTargetSmartHost = "varunagroup-de.mail.protection.outlook.com" 18 | 19 | # Create new send connector 20 | # The new send connector is not enabled by default! 21 | # Check additional settings, e.g., MaxMessageSize, before enabling the send connector 22 | New-SendConnector -Name $sendConnectorName -AddressSpaces * -CloudServicesMailEnabled $true ` 23 | -Fqdn $sendConnectorFqdn ` 24 | -SmartHosts $sendConnectorTargetSmartHost ` 25 | -SourceTransportServers $sendConnectorSourceTransportServers` 26 | -MaxMessageSize $sendConnectorMaxMessageSize ` 27 | -RequireTLS $true -DNSRoutingEnabled $false ` 28 | -TlsAuthLevel CertificateValidation -Enabled:$false -------------------------------------------------------------------------------- /Exchange Server/Copy-ReceiveConnector/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Exchange Server/Copy-ReceiveConnector/README.md: -------------------------------------------------------------------------------- 1 | # Copy-ReceiveConnector.ps1 2 | 3 | Copy a selected receive connector and it's configuration and permissions to other Exchange Servers 4 | 5 | ## Description 6 | 7 | This script copies a receive connector from a source Exchange Server to a single target Exchange server or to all Exchange servers. 8 | 9 | Configured permissions are copied as well, if required. 10 | 11 | ## Requirements 12 | 13 | - Windows Server 2016, Windows Server 2019 14 | - Exchange Server 2013/2016/2019 Management Shell 15 | 16 | ## Parameters 17 | 18 | ### ConnectorName 19 | 20 | Name of the connector the new IP addresses should be added to 21 | 22 | ### SourceServer 23 | 24 | Name of the receive connector to copy 25 | 26 | ### TargetServer 27 | 28 | Target Exchange server to copy the selected receive connector to 29 | 30 | ### DomainController 31 | 32 | Domain Controller name 33 | 34 | ### CopyToAllOther 35 | 36 | Switch to copy to all other Exchange servers 37 | 38 | ### CopyPermissions 39 | 40 | Copy non inherited source receive AD permissions to target receive connector. Inherited permissions will not be copied 41 | 42 | ### MoveToFrontend 43 | 44 | Change source connector transport role to FrontendTransport. This is required when you copy a receive connector from Exchange 2007 to Exchange 2013+ 45 | 46 | ### ResetBindings 47 | 48 | Do not copy bindings but reset receive connector network bindings to 0.0.0.0:25 49 | 50 | ### UpdateExistingConnector 51 | 52 | Update an existing receive connector without confirmation prompt. 53 | 54 | ### ViewEntireForest 55 | 56 | View entire Active Directory forest 57 | 58 | ## Examples 59 | 60 | ``` PowerShell 61 | .\Copy-ReceiveConnector.ps1 -SourceServer MBX01 -ConnectorName nikos-one-RC2 -TargetServer MBX2 -DomainController MYDC1.mcsmemail.de 62 | ``` 63 | 64 | Copy Exchange 2013 receive connector nikos-one-RC2 from server MBX01 to server MBX2 65 | 66 | ``` PowerShell 67 | .\Copy-ReceiveConnector.ps1 -SourceServer MBX01 -ConnectorName nikos-one-RC1 -CopyToAllOther -DomainController MYDC1.mcsmemail.de 68 | ``` 69 | 70 | Copy Exchange 2013 receive connector nikos-one-RC2 from server MBX01 to all other Exchange 2013 servers 71 | 72 | ``` PowerShell 73 | .\Copy-ReceiveConnector.ps1 -SourceServer MBX2007 -ConnectorName "varunagroup relay" -TargetServer MBX01 -MoveToFrontend -ResetBindings -DomainController MYDC1.mcsmemail.de 74 | ``` 75 | 76 | Copy Exchange 2013 receive connector "nikos-two relay" from Exchange 2007 server MBX2007 to Exchange 2013+ server MBX01 and reset network binding 77 | 78 | ``` PowerShell 79 | .\Copy-ReceiveConnector.ps1 -SourceServer MBX01 -ConnectorName MYRECEIVECONNECTOR -CopyToAllOther -DomainController MYDC1.mcsmemail.de -UpdateExitingConnector 80 | ``` 81 | 82 | Copy Exchange 2013/2016/2019 receive connector MYRECEIVECONNECTOR from server MBX01 to all other Exchange 2013+ servers without confirmation prompt if connectors already exists 83 | 84 | ## Note 85 | 86 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 87 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 88 | 89 | ## Credits 90 | 91 | Written by: Thomas Stensitzki 92 | 93 | ### Stay connected 94 | 95 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 96 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 97 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 98 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 99 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 100 | 101 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 102 | 103 | * Website: [https://granikos.eu](https://granikos.eu) 104 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 105 | -------------------------------------------------------------------------------- /Exchange Server/Export-MessageQueue/Export-MessageQueue.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Export messages from a transport queue to file system for manual replay 4 | 5 | Thomas Stensitzki 6 | 7 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 8 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 9 | 10 | Version 1.2, 2022-09-05 11 | 12 | Leave ideas, comments, and suggestions at GitHub 13 | 14 | .LINK 15 | https://github.com/Apoc70/Export-MessageQueue 16 | 17 | .DESCRIPTION 18 | 19 | This script suspends a transport queue, exports the messages to the local file system. After successful export the messages are optionally deleted from the queue. 20 | 21 | .NOTES 22 | Requirements 23 | - Exchange Server 2016+ 24 | - Windows Server 2016+ 25 | - Utilizes global functions library 26 | 27 | Revision History 28 | -------------------------------------------------------------------------------- 29 | 1.0 Initial community release 30 | 1.1 Some PowerShell hygiene 31 | 1.2 PowerSHell Typo fixed and updated for Exchange Server 2019 32 | 33 | .PARAMETER Queue 34 | Full name of the transport queue, e.g. SERVER\354 35 | Use Get-Queue -Server SERVERNAME to identify message queue 36 | 37 | .PARAMETER Path 38 | Path to folder for exprted messages 39 | 40 | .PARAMETER DeleteAfterExport 41 | Switch to delete per Exchange Server subfolders and creating new folders 42 | 43 | .EXAMPLE 44 | Export messages from queue MCMEP01\45534 to D:\ExportedMessages and do not delete messages after export 45 | 46 | .\Export-MessageQueue -Queue MCMEP01\45534 -Path D:\ExportedMessages 47 | 48 | .EXAMPLE 49 | Export messages from queue MCMEP01\45534 to D:\ExportedMessages and delete messages after export 50 | 51 | .\Export-MessageQueue -Queue MCMEP01\45534 -Path D:\ExportedMessages -DeleteAfterExport 52 | 53 | #> 54 | param( 55 | [parameter(Mandatory=$true,HelpMessage='Transport queue holding messages to be exported (e.g. SERVER\354)')] 56 | [string] $Queue, 57 | [parameter(Mandatory=$true,HelpMessage='File path to local folder for exprted messages (e.g. E:\Export)')] 58 | [string] $Path, 59 | [switch] $DeleteAfterExport 60 | ) 61 | 62 | # Set-StrictMode -Version Latest 63 | 64 | # Implementation of global module 65 | # Import GlobalFunctions 66 | if($null -ne (Get-Module -Name GlobalFunctions -ListAvailable).Version) { 67 | Import-Module -Name GlobalFunctions 68 | } 69 | else { 70 | Write-Warning -Message 'Unable to load GlobalFunctions PowerShell module.' 71 | Write-Warning -Message 'Open an administrative PowerShell session and run Import-Module GlobalFunctions' 72 | Write-Warning -Message 'Please check http://bit.ly/GlobalFunctions for further instructions' 73 | exit 74 | } 75 | 76 | $ScriptDir = Split-Path -Path $script:MyInvocation.MyCommand.Path 77 | $ScriptName = $MyInvocation.MyCommand.Name 78 | $logger = New-Logger -ScriptRoot $ScriptDir -ScriptName $ScriptName -LogFileRetention 14 79 | $logger.Write('Script started') 80 | $logger.Write(('Working on message queue {0}, Export folder: {1}, DeleteAfterExport: {2}' -f $Queue, $Path, $DeleteAfterExport)) 81 | 82 | ### FUNCTIONS ----------------------------- 83 | 84 | function Request-Choice { 85 | [CmdletBinding()] 86 | param([string]$Caption) 87 | $choices = [System.Management.Automation.Host.ChoiceDescription[]]@("&Yes","&No") 88 | [int]$defaultChoice = 1 89 | 90 | $choiceReturn = $Host.UI.PromptForChoice($Caption, "", $choices, $defaultChoice) 91 | 92 | return $choiceReturn 93 | } 94 | 95 | function Check-Folders { 96 | # Check, if export folder exists 97 | if(!(Test-Path -Path $Path)) { 98 | # Folder does not exist, lets create a new root folder 99 | New-Item -Path $Path -ItemType Directory | Out-Null 100 | 101 | $logger.Write(('Folder {0} created' -f $Path)) 102 | } 103 | } 104 | 105 | 106 | function Check-Queue { 107 | # Check message queue 108 | $messageCount = -1 109 | try { 110 | $messageQueue = Get-Queue $Queue 111 | $messageCount = $messageQueue.MessageCount 112 | 113 | $logger.Write(('{0} message(s) found in queue {1}' -f $messageCount, $Queue)) 114 | } 115 | catch { 116 | $logger.Write(('Queue {0} cannot be accessed' -f $Queue)) 117 | } 118 | $messageCount 119 | } 120 | 121 | function Export-Messages { 122 | # Export suspended messages 123 | try { 124 | # Suspend messages in queue 125 | $logger.Write(('Suspending queue {0}' -f $Queue)) 126 | Get-Queue $Queue | Get-Message -ResultSize Unlimited | Suspend-Message -Confirm:$false 127 | 128 | # Fetch suspended messages 129 | $logger.Write(('Fetching suspended messages from queue {0}' -f $Queue)) 130 | 131 | $messages = @(Get-Queue $Queue | Get-Message -ResultSize Unlimited | Where-Object{$_.Status -eq "Suspended"} ) 132 | 133 | $logger.Write( ('{0} suspended messages fetched from queue {1}' -f $messages.Count, $Queue) ) 134 | 135 | # Export fetched messages 136 | $messages | ForEach-Object {$m++;Export-Message $_.Identity | AssembleMessage -Path (Join-Path -ChildPath ('{0}.eml' -f $m) -Path $Path)} 137 | } 138 | catch { 139 | # get error record 140 | [Management.Automation.ErrorRecord]$e = $_ 141 | 142 | # retrieve information about runtime error 143 | $info = [PSCustomObject]@{ 144 | Exception = $e.Exception.Message 145 | Reason = $e.CategoryInfo.Reason 146 | Target = $e.CategoryInfo.TargetName 147 | Script = $e.InvocationInfo.ScriptName 148 | Line = $e.InvocationInfo.ScriptLineNumber 149 | Column = $e.InvocationInfo.OffsetInLine 150 | } 151 | 152 | Write-Error $info 153 | } 154 | } 155 | 156 | function Delete-Messages { 157 | # Delete suspended messages from queue 158 | 159 | $logger.Write( ('Delete suspended messages from queue {0}' -f $Queue) ) 160 | 161 | Get-Message -Queue $Queue -ResultSize Unlimited | Where-Object{$_.Status -eq "Suspened"} | Remove-Message -WithNDR $false -Confirm:$false 162 | } 163 | 164 | 165 | # MAIN #################################################### 166 | 167 | # 1. Check export folder 168 | Check-Folders 169 | 170 | # 2. Check queue 171 | if((Check-Queue -gt 0)) { 172 | if((Request-Choice -Caption ('Do you want to suspend and export all messages in queue {0}?' -f $Queue)) -eq 0) { 173 | # Yes, we want to suspend and delete messages 174 | Export-Messages 175 | } 176 | else { 177 | # No, we do not want to delete message 178 | $logger.Write("User choice: Do not suspend and export messages") 179 | } 180 | if($DeleteAfterExport) { 181 | if((Request-Choice -Caption ('Do you want to DELETE all suspended messages from queue {0}?' -f $Queue)) -eq 0) { 182 | $logger.Write("User choice: DELETE suspended") 183 | Write-Output "Suspended messages will be deleted WITHOUT sending a NDR!" 184 | Delete-Messages 185 | } 186 | else { 187 | $logger.Write("User choice: DO NOT DELETE suspended") 188 | Write-Output "Exported messages have NOT been deleted from queue!" 189 | Write-Output "Remove messages manually and be sure, if you want to send a NDR!" 190 | } 191 | } 192 | } 193 | else { 194 | Write-Output ('Queue {0} does not contain any messages' -f $Queue) 195 | $logger.Write(('Queue {0} does not contain any messages' -f $Queue)) 196 | } 197 | 198 | $logger.Write("Script finished") 199 | Write-Host "Script finished" -------------------------------------------------------------------------------- /Exchange Server/Export-MessageQueue/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Exchange Server/Export-MessageQueue/README.md: -------------------------------------------------------------------------------- 1 | # Export-MessageQueue.ps1 2 | 3 | Export messages from a transport queue to file system for manual replay 4 | 5 | ## Description 6 | 7 | This script suspends a transport queue, exports the messages to the local file system. After successful export the messages are optionally deleted from the queue. 8 | 9 | This script utilizes the GlobalFunctions library [https://github.com/Apoc70/GlobalFunctions](https://github.com/Apoc70/GlobalFunctions) 10 | 11 | ## Parameters 12 | 13 | ### Queue 14 | 15 | Full name of the transport queue, e.g. SERVER\354 16 | Use Get-Queue -Server SERVERNAME to identify message queue 17 | 18 | ### Path 19 | 20 | Path to folder for exported messages 21 | 22 | ### DeleteAfterExport 23 | 24 | Switch to delete per Exchange Server subfolders and creating new folders 25 | 26 | ## Examples 27 | 28 | ``` PowerShell 29 | .\Export-MessageQueue -Queue MCMEP01\45534 -Path D:\ExportedMessages 30 | ``` 31 | 32 | Export messages from queue MCMEP01\45534 to D:\ExportedMessages and do not delete messages after export 33 | 34 | ``` PowerShell 35 | .\Export-MessageQueue -Queue MCMEP01\45534 -Path D:\ExportedMessages -DeleteAfterExport 36 | ``` 37 | 38 | Export messages from queue MCMEP01\45534 to D:\ExportedMessages and delete messages after export 39 | 40 | ## Note 41 | 42 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 43 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 44 | 45 | ## Credits 46 | 47 | Written by: Thomas Stensitzki 48 | 49 | ### Stay connected 50 | 51 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 52 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 53 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 54 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 55 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 56 | 57 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 58 | 59 | * Website: [https://granikos.eu](https://granikos.eu) 60 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 61 | -------------------------------------------------------------------------------- /Exchange Server/Export-ModernPublicFolderStatistics/Export-PublicFolderStatisticsToExcel.ps1: -------------------------------------------------------------------------------- 1 | # Parameter section 2 | [CmdletBinding()] 3 | param( 4 | [switch]$OpenExcel, 5 | [switch]$SendMail, 6 | [string]$MailFrom = '', 7 | [string]$MailTo = '', 8 | [string]$MailServer = '' 9 | ) 10 | 11 | $ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path 12 | 13 | # Use current date as file timestamp 14 | $now = Get-Date -Format 'yyyy-MM-dd' 15 | 16 | $cssFile = Join-Path -Path $ScriptDir -ChildPath styles.css 17 | $reportTitle = "Public Folder Statistics - $($now)" 18 | 19 | function Import-PublicFolderStatsToExcelWithChart { 20 | param ( 21 | [string]$csvPath = "PublicFolderSummary.csv", 22 | [string]$excelPath = "PublicFolderSummary.xlsx" 23 | ) 24 | 25 | # Check if the ImportExcel module is installed 26 | if (-not (Get-Module -ListAvailable -Name ImportExcel)) { 27 | Write-Host "ImportExcel module is not installed. Installing now..." 28 | Install-Module -Name ImportExcel -Force -Scope CurrentUser 29 | } 30 | 31 | Remove-Item $excelPath -ErrorAction Ignore 32 | 33 | # Import data from CSV 34 | $data = Import-Csv -Path $csvPath 35 | 36 | $workSheetName = "PublicFolderStats" 37 | 38 | # Export data to Excel 39 | $excel = $data | Export-Excel -Path $excelPath -WorksheetName $workSheetName -ClearSheet -TableName PublicFolder -AutoSize -PassThru -AutoNameRange 40 | 41 | 42 | 43 | # some chart parameters 44 | $width = 1000 45 | $height = 500 46 | $row = 1 47 | $column = 1 48 | 49 | # Add chart sheet 50 | $null = Add-Worksheet -ExcelPackage $excel -WorksheetName 'Overview' -Activate -MoveToEnd 51 | 52 | # Add a line chart to the Excel file 53 | $excelChartParams = @{ 54 | Worksheet = $excel.Overview 55 | ChartType = "LineMarkersStacked" 56 | Title = "Entwicklung der Public Folder (Anzahl)" 57 | #XRange = "A2:A" + ($data.Count + 1) 58 | #YRange = "B2:B" + ($data.Count + 1) #+ ",D2:D" + ($data.Count + 1) # PublicFolderCount and PublicFolderItemSizeInMB columns 59 | XRange = ('{0}!Date' -f $workSheetName) 60 | YRange = ('{0}!Count' -f $workSheetName)# ,('{0}!SizeInMB' -f $workSheetName) 61 | SeriesHeader = "Anzahl Public Folder"# , "Größe in MB" 62 | XAxisTitleText = "Datum" 63 | XAxisTitleSize = 10 64 | #XAxisNumberformat = "Date-Time" 65 | YAxisTitleText = "Anzahl" 66 | YAxisTitleSize = 10 67 | LegendPosition = "bottom" 68 | Row = $row 69 | Column = $column 70 | Width = $width 71 | Height = $height 72 | 73 | } 74 | 75 | Add-ExcelChart @excelChartParams 76 | 77 | # Add chart sheet 78 | $null = Add-Worksheet -ExcelPackage $excel -WorksheetName 'Size' -Activate -MoveToEnd 79 | 80 | # Add a line chart to the Excel file 81 | $excelChartParams = @{ 82 | Worksheet = $excel.Size 83 | ChartType = "LineMarkersStacked" 84 | Title = "Entwicklung der Public Folder (Size)" 85 | #XRange = "A2:A" + ($data.Count + 1) 86 | #YRange = "B2:B" + ($data.Count + 1) #+ ",D2:D" + ($data.Count + 1) # PublicFolderCount and PublicFolderItemSizeInMB columns 87 | XRange = ('{0}!Date' -f $workSheetName) 88 | YRange = ('{0}!SizeInMB' -f $workSheetName) 89 | SeriesHeader = "Size in MB" 90 | XAxisTitleText = "Datum" 91 | XAxisTitleSize = 10 92 | YAxisNumberformat = "#,##0" 93 | YAxisTitleText = "Size [MB]" 94 | YAxisTitleSize = 10 95 | LegendPosition = "bottom" 96 | Row = $row 97 | Column = $column 98 | Width = $width 99 | Height = $height 100 | 101 | } 102 | 103 | Add-ExcelChart @excelChartParams 104 | 105 | # Add chart sheet 106 | $null = Add-Worksheet -ExcelPackage $excel -WorksheetName 'First Level' -Activate -MoveToEnd 107 | 108 | # Add a line chart to the Excel file 109 | $excelChartParams = @{ 110 | Worksheet = $excel."First Level" 111 | ChartType = "LineMarkersStacked" 112 | Title = "Public Folder Erste Ebene (Anzahl)" 113 | #XRange = "A2:A" + ($data.Count + 1) 114 | #YRange = "B2:B" + ($data.Count + 1) #+ ",D2:D" + ($data.Count + 1) # PublicFolderCount and PublicFolderItemSizeInMB columns 115 | XRange = ('{0}!Date' -f $workSheetName) 116 | YRange = ('{0}!RootFolders' -f $workSheetName) 117 | SeriesHeader = "Anzahl" 118 | XAxisTitleText = "Datum" 119 | XAxisTitleSize = 10 120 | YAxisNumberformat = "#,##0" 121 | YAxisTitleText = "Anzahl" 122 | YAxisTitleSize = 10 123 | LegendPosition = "bottom" 124 | Row = $row 125 | Column = $column 126 | Width = $width 127 | Height = $height 128 | 129 | } 130 | 131 | Add-ExcelChart @excelChartParams 132 | 133 | if($OpenExcel) { 134 | # Open the Excel file 135 | Close-ExcelPackage $excel -Show 136 | } 137 | esle{ 138 | # Save the Excel file 139 | Close-ExcelPackage $excel 140 | } 141 | 142 | Write-Host "Data and chart have been successfully exported to $excelPath" 143 | } 144 | 145 | # Call the function to create the Excel file 146 | Import-PublicFolderStatsToExcelWithChart 147 | 148 | if ($SendMail) { 149 | 150 | 151 | 152 | $head = @" 153 | 154 | $($reportTitle) 155 | 156 |

$($reportTitle)

157 |

Here are the current public folder statistics.

158 | "@ 159 | 160 | [string]$htmlreport = ConvertTo-Html -Body $html -Head $head -Title $reportTitle 161 | 162 | Send-Mail -From $MailFrom -To $MailTo -SmtpServer $MailServer -MessageBody $htmlreport -Subject $reportTitle -Attachments $excelPath -BodyAsHtml 163 | } -------------------------------------------------------------------------------- /Exchange Server/Export-ModernPublicFolderStatistics/styles.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | body { 3 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 4 | font-size: 10pt; 5 | } 6 | h1 { 7 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 8 | font-size: 16px; 9 | } 10 | h2 { 11 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 12 | font-size: 14px; 13 | } 14 | h3 { 15 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 16 | font-size: 12px; 17 | } 18 | table { 19 | font: 10pt Verdana, Geneva, Arial, Helvetica, sans-serif; 20 | border: 1px solid black; 21 | border-collapse: 22 | collapse; font-size: 10pt; 23 | } 24 | table th { 25 | border: 1px solid black; 26 | background: #dddddd; 27 | padding: 5px; 28 | color: #000000; 29 | } 30 | table td { 31 | border: 1px solid black; 32 | padding: 5px; 33 | } 34 | table td.pass { 35 | background: #7FFF00; 36 | } 37 | table td.warn { 38 | background: #FFE600; 39 | } 40 | table td.fail { 41 | background: #FF0000; color: #ffffff; 42 | } 43 | table td.info { 44 | background: #85D4FF; 45 | } 46 | .error { 47 | background: #FF0000; color: #ffffff; 48 | } 49 | .danger {background-color: red} 50 | .warn {background-color: yellow} 51 | .ok {background-color: green} -------------------------------------------------------------------------------- /Exchange Server/Export-PublicFolderPermissions/Export-PublicFolderPermissions.ps1: -------------------------------------------------------------------------------- 1 | $ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path 2 | 3 | # Use current date as file timestamp 4 | $now = Get-Date -Format 'yyyy-MM-dd' 5 | 6 | $cssFile = Join-Path -Path $ScriptDir -ChildPath styles.css 7 | 8 | $reportFileName = "Public Folder Permissions - $($now).csv" 9 | 10 | $pf = Get-PublicFolder -ResultSize Unlimited -Recurse 11 | 12 | $pf | .\Report-PFPermissions.ps1 | Export-Csv -Path (Join-Path -Path $ScriptDir -ChildPath $reportFileName) -NoTypeInformation -Encoding UTF8 -Force -------------------------------------------------------------------------------- /Exchange Server/Export-PublicFolderPermissions/Report-PFPermissions.ps1: -------------------------------------------------------------------------------- 1 | # Copyright Frank Carius 2 | # https://www.msxfaq.de/exchange/tools/auswertung/reportpfpermissions.htm 3 | 4 | Begin { 5 | } 6 | 7 | Process { 8 | $pf = $_ 9 | if($pf.parentpath -eq '\') { 10 | $folder = $pf.parentpath + $pf.name 11 | } 12 | else { 13 | $folder = $pf.parentpath + "\" + $pf.name 14 | } 15 | write-host "Processing Folder:" $folder 16 | $permission = get-PublicFolderClientPermission -identity $pf.Identity 17 | foreach ($perm in $permission) { 18 | $rights = [string]$perm.accessRights 19 | $User = [string]$perm.User 20 | # write-host "Rechte" $rights "User:" $User 21 | $pso = New-Object PSObject 22 | Add-Member -InputObject $pso noteproperty Folder $folder 23 | Add-Member -InputObject $pso noteproperty User $User 24 | Add-Member -InputObject $pso noteproperty AccessRights $rights 25 | $pso 26 | } 27 | } 28 | 29 | End { 30 | } -------------------------------------------------------------------------------- /Exchange Server/Get-ExchangeADVersion/Get-ExchangeADVersion.ps1: -------------------------------------------------------------------------------- 1 | # This script retrieves the version of the Active Directory schema used by Exchange. 2 | # It uses the ADSI provider to access the Active Directory schema and retrieves the version information. 3 | 4 | # schema and object version strings 5 | # The version strings are used to map the schema and object versions to the corresponding Exchange version. 6 | # The mapping is done using a hashtable, where the keys are the schema and object version strings, and the values are the corresponding Exchange version strings. 7 | # The hashtable is used to simplify the mapping process and make it easier to retrieve the Exchange version based on the schema and object versions. 8 | $VersionStrings = @{ 9 | '17003.16763.13243' = @{Long = 'Exchange Server 2019 CU15'; Short = 'Ex19CU15' } 10 | '17003.16762.13243' = @{Long = 'Exchange Server 2019 CU14'; Short = 'Ex19CU14' } 11 | '17003.16761.13243' = @{Long = 'Exchange Server 2019 CU13'; Short = 'Ex19CU13' } 12 | '17003.16760.13243' = @{Long = 'Exchange Server 2019 CU12'; Short = 'Ex19CU12' } 13 | '17003.16759.13242' = @{Long = 'Exchange Server 2019 CU11'; Short = 'Ex19CU11' } 14 | 15 | '15334.16223.13243' = @{Long = 'Exchange Server 2016 CU23'; Short = 'Ex16CU23' } 16 | '15334.16222.13242' = @{Long = 'Exchange Server 2016 CU22'; Short = 'Ex16CU22' } 17 | 18 | '15312.16133.13237' = @{Long = 'Exchange Server 2013 CU23'; Short = 'Ex13CU23' } 19 | } 20 | 21 | 22 | $exchangeVersion = $null 23 | 24 | # Forest version 25 | # This script retrieves the version of the Active Directory schema used by Exchange. 26 | $NetBiosDomainName = 'varunagroup' 27 | 28 | $RootDSE = ([ADSI]"").distinguishedName 29 | $schemaVersion = ([ADSI]"LDAP://CN=ms-Exch-Schema-Version-Pt,CN=Schema,CN=Configuration,$RootDSE").rangeUpper.ToString() 30 | $adObjectVersion = ([ADSI]"LDAP://cn=$($NetBiosDomainName),cn=Microsoft Exchange,cn=Services,cn=Configuration,$RootDSE").objectVersion.ToString() 31 | $NetBiosDomainNameObjectVersion = ([ADSI]"LDAP://CN=Microsoft Exchange System Objects,$RootDSE").objectVersion.ToString() 32 | 33 | Write-Host ('Schema rangeUpper : {0}' -f $schemaVersion) 34 | Write-Host ('Forest objectVersion: {0}' -f $adObjectVersion ) 35 | 36 | # Domain version 37 | 38 | $RootDSE = ([ADSI]"").distinguishedName 39 | Write-Host ('Domain objectVersion: {0}' -f $NetBiosDomainNameObjectVersion) 40 | 41 | # Exchange version 42 | $exchangeVersion = $VersionStrings[('{0}.{1}.{2}' -f $schemaVersion, $adObjectVersion, $NetBiosDomainNameObjectVersion)].Long 43 | if ($null -eq $exchangeVersion) { 44 | Write-Host 'No matching Exchange schema/object version found!' 45 | } 46 | else { 47 | Write-Host ('Exchange Version : {0}' -f $exchangeVersion) 48 | } -------------------------------------------------------------------------------- /Exchange Server/Get-ExchangeEnvironmentReport/EnvironmentReport.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 3 | font-size: 10pt; 4 | } 5 | h1 { 6 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 7 | font-size: 16px; 8 | font-weight 9 | } 10 | h2 { 11 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 12 | font-size: 14px; 13 | } 14 | h3 { 15 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 16 | font-size: 12px; 17 | } 18 | h4 { 19 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 20 | font-size: 10px; 21 | } 22 | 23 | /* Header, first table row showing labels for totals */ 24 | table.header { 25 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 26 | border: 0px; 27 | padding: 3px; 28 | font-size: 11px; 29 | } 30 | table tr.header { 31 | background: #009900; 32 | } 33 | table th.header { 34 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 35 | color: #ffffff; 36 | } 37 | 38 | /* Site overview */ 39 | table.overview { 40 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 41 | font-size: 11px; 42 | font-weicht: bold; 43 | padding: 3px; 44 | border: 0px; 45 | width: 100% 46 | } 47 | table th.overview { 48 | color: #ffffff; 49 | } 50 | 51 | table tr.overviewcolheader { 52 | background: #000099; 53 | } 54 | table tr.overviewcolsubheader { 55 | background: #0000FF; 56 | } 57 | 58 | table tr.pre2007overviewcolheader { 59 | background: #880099; 60 | } 61 | table tr.pre2007overviewcolsubheader { 62 | background: #8800CC; 63 | } 64 | 65 | table tr.alternaterow { 66 | background: #dddddd 67 | } 68 | table td.roledata { 69 | text-align: center; 70 | background: #00FF00; 71 | } 72 | 73 | 74 | /* Subheader, Exchange Server Versions, Org and roles */ 75 | table tr.subheader { 76 | background: #009900; 77 | } 78 | table th.subheader { 79 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif, monospace, Lucida Console; 80 | color: #ffffff; 81 | font-size: 11px; 82 | text-align: center; 83 | } 84 | 85 | /* Header data, Mailbox numbers in header table */ 86 | table tr.headerdata { 87 | background: #dddddd; 88 | } 89 | 90 | table td.headerdata { 91 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 92 | color: #000000; 93 | font-size: 11px; 94 | text-align: center; 95 | } 96 | 97 | table.dagsummary { 98 | border: 0px; 99 | padding: 3px; 100 | width: 100%; 101 | font-size:8pt; 102 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 103 | text-align: center; 104 | } 105 | table tr.dagsummary { 106 | text-align: center; 107 | background: #FF8000; 108 | } 109 | table tr.dagsummarynondag { 110 | text-align: center; 111 | background: #FF8000; 112 | } 113 | 114 | table.databases { 115 | border: 0px; 116 | padding: 3px; 117 | width: 100%; 118 | font-size:9pt; 119 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 120 | } 121 | table tr.databases { 122 | background: #FFD700; 123 | text-align: center; 124 | } 125 | 126 | .dagtablefooter { 127 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 128 | font-size:8pt; 129 | } 130 | 131 | .center { 132 | text-align: center; 133 | } 134 | 135 | .alert { 136 | color: red; 137 | } 138 | 139 | .highlight { 140 | color: violet; 141 | } 142 | 143 | .tooltip { 144 | position: relative; 145 | display: inline-block; 146 | border-bottom: 1px dotted black; 147 | } 148 | 149 | .tooltip .tooltiptext { 150 | visibility: hidden; 151 | width: 120px; 152 | background-color: #555; 153 | color: #fff; 154 | text-align: center; 155 | border-radius: 6px; 156 | padding: 5px 0; 157 | position: absolute; 158 | z-index: 1; 159 | bottom: 125%; 160 | left: 50%; 161 | margin-left: -60px; 162 | opacity: 0; 163 | transition: opacity 0.3s; 164 | } 165 | 166 | .tooltip .tooltiptext::after { 167 | content: ""; 168 | position: absolute; 169 | top: 100%; 170 | left: 50%; 171 | margin-left: -5px; 172 | border-width: 5px; 173 | border-style: solid; 174 | border-color: #555 transparent transparent transparent; 175 | } 176 | 177 | .tooltip:hover .tooltiptext { 178 | visibility: visible; 179 | opacity: 1; 180 | } -------------------------------------------------------------------------------- /Exchange Server/Get-ExchangeEnvironmentReport/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Exchange Server/Get-ExchangeEnvironmentReport/README.md: -------------------------------------------------------------------------------- 1 | # Get-ExchangeEnvironmentReport 2 | 3 | Creates an HTML report describing the On-Premises Exchange environment. 4 | 5 | Based on the original 1.6.2 version by Steve Goodman 6 | 7 | ## Description 8 | 9 | This script creates an HTML report showing the following information about an Exchange 2019, 2016, 2013, 2010, and, to a lesser extent, 2007 and 2003 environment. 10 | 11 | The HTML report requires the CSS file that is part of this repository for proper HTML formatting. 12 | 13 | The report shows the following: 14 | 15 | * As summary 16 | * Total number of servers per Exchange Server version 17 | * Total number of mailboxes per On-Premises Exchange Server version, Office 365, and Exchange Organisation 18 | * Total number of Exchange Server functional roles 19 | 20 | * Per Active Directory Site 21 | * Total number of mailboxes 22 | * Internal, External, and CAS Array names 23 | * Exchange Server computers 24 | * Product version 25 | * Service Pack, Update Rollup, and/or Cumulative Update 26 | * Number of preferred and maximum active databases 27 | * Functional Roles 28 | * Operating System with Service Pack 29 | 30 | * Per Database Availability Group 31 | * Total number of member servers 32 | * List of member servers 33 | * DAG databases 34 | * Number of mailboxes and average mailbox size 35 | * Number of archive mailboxes and average archive mailbox size 36 | * Database size 37 | * Database whitespace 38 | * Disk space available for database and log file volume 39 | * Last full backup timestamp 40 | * Circular logging enabled 41 | * Mailbox server hosting an active copy 42 | * List of mailbox servers hosting database copies 43 | 44 | * Per Database (Non-DAG, pre-DAG Exchange Server) 45 | * Storage group and database name 46 | * Server name hosting the database 47 | * Number of mailboxes and average mailbox size 48 | * Number of archive mailboxes and average archive mailbox size 49 | * Database size 50 | * Database whitespace 51 | * Disk space available for database and log file volume 52 | * Last full backup timestamp 53 | * Circular logging enabled 54 | 55 | The PowerShell script does not gather information on public folders or analyzes Exchange cluster technologies like Exchange Server 2007/2003 CCR/SCR. 56 | 57 | ## Requirements 58 | 59 | * Exchange Server Management Shell 2010 or newer 60 | * WMI and Remote Registry access from the computer running the script to all internal Exchange Servers 61 | * CSS file for HTML formatting 62 | 63 | ## Release 64 | 65 | * 2.0 : Initial Community Release of the updated original script 66 | * 2.1 : Table header label updated for a more consistent labeling 67 | * 2.2 : Bug fixes and enhancements 68 | * CCS fixes for Html header tags (issue #5) 69 | * New script parameter _ShowDriveNames_ added to optionally show drive names for EDB/LOG file paths in database table (issue #4) 70 | * Exchange organization name added to report header 71 | * 2.4 : Bug fix for empty ExternalUrl parameter values 72 | * 2.5 : Issue #6 fixed - CSS file check added 73 | * 2.7 : ShowDisconnectedMailboxCount added 74 | 75 | ## Example Report 76 | 77 | ![Example Report](/Exchange%20Server/Get-ExchangeEnvironmentReport/images/screenshot.png) 78 | 79 | ## Parameters 80 | 81 | ### HTMLReport 82 | 83 | File name to write HTML Report to 84 | 85 | ### SendMail 86 | 87 | Send Mail after completion. Set to $True to enable. If enabled, -MailFrom, -MailTo, -MailServer are mandatory 88 | 89 | ### MailFrom 90 | 91 | Email address to send from. Passed directly to Send-MailMessage as -From 92 | 93 | ### MailTo 94 | 95 | Email address to send to. Passed directly to Send-MailMessage as -To 96 | 97 | ### MailServer 98 | 99 | SMTP Mail server to attempt to send through. Passed directly to Send-MailMessage as -SmtpServer 100 | 101 | ### ViewEntireForest 102 | 103 | By default, true. Set the option in Exchange 2007 or 2010 to view all Exchange servers and recipients in the forest. 104 | 105 | ### ServerFilter 106 | 107 | Use a text based string to filter Exchange Servers by, e.g., NL-* 108 | Note the use of the wildcard (*) character to allow for multiple matches. 109 | 110 | ### ShowDriveNames 111 | 112 | Include drive names of EDB file path and LOG file folder in database report table 113 | 114 | ### ShowDisconnectedMailboxCount 115 | 116 | Show the number of disconnected mailboxes in the database report table 117 | 118 | ### ShowProvisioningStatus 119 | 120 | Show IsExludedFromProvisioning or IsExcludedFromProvisioningByOperator status in the report 121 | 122 | ### CssFileName 123 | 124 | The filename containing the Cascading Style Sheet (CSS) information fpr the HTML report 125 | Default: EnvironmentReport.css 126 | 127 | ## Examples 128 | 129 | ### Example 1 130 | 131 | Generate an HTML report and send the result as HTML email with attachment to the specified recipient using a dedicated smart host 132 | 133 | ``` PowerShell 134 | .\Get-ExchangeEnvironmentReport.ps1 -HTMReport ExchangeEnvironment.html -SendMail -ViewEntireForet $true -MailFrom roaster@mcsmemail.de -MailTo grillmaster@mcsmemail.de -MailServer relay.mcsmemail.de 135 | ``` 136 | 137 | ### Example 2 138 | 139 | Generate an HTML report and save the report as 'report.html' 140 | 141 | ``` PowerShell 142 | .\Get-ExchangeEnvironmentReport.ps1 -HTMLReport .\report.html 143 | ``` 144 | 145 | ### Example 3 146 | 147 | Generate the HTML report including EDB and LOG drive names 148 | 149 | ``` PowerShell 150 | .\Get-ExchangeEnvironmentReport.ps1 -ShowDriveNames -HTMLReport .\report.html 151 | ``` 152 | 153 | ### Example 4 154 | 155 | Generate the HTML report using a custom CSS file 156 | 157 | ``` PowerShell 158 | .\Get-ExchangeEnvironmentReport.ps1 -HTMLReport .\report.html -CssFileName MyCustomCSSFile.css 159 | ``` 160 | 161 | ## Note 162 | 163 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 164 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 165 | 166 | ## Credits 167 | 168 | Based on the original 1.6.2 version by Steve Goodman. 169 | 170 | ### Stay connected 171 | 172 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 173 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 174 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 175 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 176 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 177 | 178 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 179 | 180 | * Website: [https://granikos.eu](https://www.granikos.eu) 181 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 182 | -------------------------------------------------------------------------------- /Exchange Server/Get-ExchangeEnvironmentReport/Run-ExchangeEnvironmentReport.ps1: -------------------------------------------------------------------------------- 1 | .\Get-ExchangeEnvironmentReport.ps1 -HTMLReport ExchangeEnvironment.html -SendMail -ViewEntireForest $true -MailFrom roaster@mcsmemail.de -MailTo grillmaster@mcsmemail.de -MailServer relay.mcsmemail.de -------------------------------------------------------------------------------- /Exchange Server/Get-ExchangeEnvironmentReport/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apoc70/PowerShell-Scripts/ef43113b389502d309e246891fa9c7a3ad5a147e/Exchange Server/Get-ExchangeEnvironmentReport/images/screenshot.png -------------------------------------------------------------------------------- /Exchange Server/Get-ExchangeServerCertificateReport/README.md: -------------------------------------------------------------------------------- 1 | # Import-EdgeSubscription.ps1 2 | 3 | This script creates an Exchange Server SSL Certificate Report 4 | 5 | ## Description 6 | 7 | The script queries all Exchange Servers an fetches the computer certificates. 8 | It generates a table report of the available certificates per server. 9 | The scripts sorts the certificates by subject name and expiry date. 10 | 11 | ## Parameters 12 | 13 | ### SendMail 14 | 15 | Send the report as an HTML email. 16 | 17 | ### MailFrom 18 | 19 | Sender address for result summary. 20 | 21 | ### MailTo 22 | 23 | Recipient address for result summary. 24 | 25 | ### MailServer 26 | 27 | SMTP Server address for sending result summary. 28 | 29 | ## Example 30 | 31 | ``` PowerShell 32 | .\Get-ExchangeCertificateReport.ps1 33 | ``` 34 | 35 | Generate an Html report file in the script directory. 36 | 37 | ## Credits 38 | 39 | Originally written by: Paul Cunningham 40 | 41 | Updated by: Thomas Stensitzki 42 | 43 | ### Stay connected 44 | 45 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 46 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 47 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 48 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 49 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 50 | 51 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 52 | 53 | * Website: [https://granikos.eu](https://granikos.eu) 54 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 55 | -------------------------------------------------------------------------------- /Exchange Server/Get-ExchangeServerCertificateReport/styles.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | body { 3 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 4 | font-size: 10pt; 5 | } 6 | h1 { 7 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 8 | font-size: 16px; 9 | } 10 | h2 { 11 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 12 | font-size: 14px; 13 | } 14 | h3 { 15 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 16 | font-size: 12px; 17 | } 18 | table { 19 | font: 10pt Verdana, Geneva, Arial, Helvetica, sans-serif; 20 | border: 1px solid black; 21 | border-collapse: 22 | collapse; font-size: 10pt; 23 | } 24 | table th { 25 | border: 1px solid black; 26 | background: #dddddd; 27 | padding: 5px; 28 | color: #000000; 29 | } 30 | table td { 31 | border: 1px solid black; 32 | padding: 5px; 33 | } 34 | table td.pass { 35 | background: #7FFF00; 36 | } 37 | table td.warn { 38 | background: #FFE600; 39 | } 40 | table td.fail { 41 | background: #FF0000; color: #ffffff; 42 | } 43 | table td.info { 44 | background: #85D4FF; 45 | } 46 | .error { 47 | background: #FF0000; color: #ffffff; 48 | } 49 | .danger {background-color: red} 50 | .warn {background-color: yellow} 51 | .ok {background-color: green} 52 | .link { 53 | font-size: 8pt; 54 | } -------------------------------------------------------------------------------- /Exchange Server/Get-NewPublicFolders/Get-NewPublicFolders.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | Get all public folders created during the las X days 5 | 6 | Thomas Stensitzki 7 | 8 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 9 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 10 | 11 | Version 2.0, 2024-10-15 12 | 13 | Please send ideas, comments and suggestions to support@granikos.eu 14 | 15 | .LINK 16 | https://scripts.granikos.eu 17 | 18 | .DESCRIPTION 19 | 20 | This script gathers all public folders created during the last X days and exportes the gathered data to a CSV file. 21 | 22 | .NOTES 23 | 24 | Requirements Legacy Public Folder 25 | - Windows Server 2008R2+ 26 | - Exchange 2010/2013 Management Shell (aka EMS) 27 | 28 | Requirements Modern Public Folder 29 | - Windows Server 2012R2+ 30 | - Exchange 2013+ Management Shell (aka EMS) 31 | 32 | Revision History 33 | -------------------------------------------------------------------------------- 34 | 1.0 Initial community release 35 | 2.0 Enhanced reporting for modern public folders 36 | 37 | .PARAMETER Days 38 | Number of last X days to filter newly created public folders. Default: 14 39 | 40 | .PARAMETER Legacy 41 | Switch to define that you want to query legacy public folders 42 | 43 | .PARAMETER ServerName 44 | Name of Exchange server hostingl egacy public folders 45 | 46 | .EXAMPLE 47 | Query legacy public folder server MYPFSERVER01 for all public folders created during the last 31 days 48 | 49 | .\Get-NewPublicFolders.ps1 -Days 31 -ServerName MYPFSERVER01 -Legacy 50 | 51 | .EXAMPLE 52 | Query modern public folders for all public folders created during the last 31 days 53 | 54 | .\Get-NewPublicFolders.ps1 -Days 31 55 | 56 | #> 57 | [CmdletBinding()] 58 | param( 59 | [int]$Days = 30, 60 | [Parameter(ParameterSetName='Legacy')] 61 | [switch]$Legacy, 62 | [Parameter(ParameterSetName='Legacy')] 63 | [string]$ServerName = 'MYSERVER', 64 | [switch]$FetchFolderPermissions, 65 | [string]$ExcludeUser = 'ExchangePublicFolderManager', 66 | [switch]$SendMail, 67 | [string]$MailFrom = "", 68 | [string]$MailTo = "", 69 | [string]$MailServer = "" 70 | ) 71 | 72 | # Fetch new public folders created over the last 7 days 73 | $CreationDate = (Get-Date).AddDays(-($Days)) 74 | 75 | $ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path 76 | 77 | # Use current date as file timestamp 78 | $now = Get-Date -Format 'yyyy-MM-dd' 79 | $CsvFilePath = Join-Path -Path $ScriptDir -ChildPath ('NewPublicFolders {0}.csv' -f ($now)) 80 | 81 | $cssFile = Join-Path -Path $ScriptDir -ChildPath styles.css 82 | $now = Get-Date -Format F 83 | $reportTitle = "New Public Folder Report - $($now)" 84 | 85 | # Gather legacy public folder statistics 86 | if($Legacy) { 87 | # Query legacy public folders 88 | 89 | Get-PublicFolderStatistics -Server $ServerName | Where-Object{$_.CreationTime -ge $CreationDate} | Select-Object -Property FolderPath,Name,ItemCount | Sort-Object -Property FolderPath | Export-Csv -Path $CsvFilePath -Encoding UTF8 -NoTypeInformation -Force -Delimiter '|' 90 | } 91 | else { 92 | # Query modern public folders 93 | 94 | $PublicFolder = Get-PublicFolderStatistics -ResultSize Unlimited | Where-Object{$_.CreationTime -ge $CreationDate} | Select-Object -Property FolderPath,Name,ItemCount,CreationTime,LastModificationTime,EntryId | Sort-Object -Property FolderPath 95 | 96 | $exportFolders = New-Object System.Collections.ArrayList -ArgumentList ( ($PublicFolder | Measure-Object).Count ) 97 | 98 | if($FetchFolderPermissions) { 99 | 100 | Write-Verbose 'Fetching Public Folder Permissions' 101 | 102 | foreach($Folder in $PublicFolder) { 103 | 104 | Write-Verbose ('{0}' -f $Folder.FolderPath) 105 | Write-Verbose ('{0}' -f $Folder.EntryId) 106 | 107 | $folderPermPath = $Folder.EntryId 108 | 109 | Write-Verbose ('Processiong {0}' -f $folderPermPath) 110 | 111 | $folderPermissions = Get-PublicFolderClientPermission $folderPermPath | Where-Object{ ($_.AccessRights -like 'Owner') -and (([string]$_.User) -ne $ExcludeUser)} | Select-Object User 112 | 113 | if( ($folderPermissions | Measure-Object).Count -ne 0) { 114 | $ownerList = [string]::Join('; ',$folderPermissions.User.DisplayName) 115 | } 116 | else { 117 | $ownerList = 'No users with OWNER role' 118 | } 119 | 120 | $property = [ordered]@{ 121 | FolderPath = $Folder.FolderPath 122 | Name = $Folder.Name 123 | ItemCount = $Folder.ItemCount 124 | CreationTime = $Folder.CreationTime 125 | LastModificationTime = $Folder.LastModificationTime 126 | Owner = $ownerList 127 | } 128 | 129 | $folderObject = New-Object -TypeName PSObject -Property $property 130 | 131 | $null = $exportFolders.Add($folderObject) 132 | } 133 | 134 | } 135 | else { 136 | 137 | foreach($Folder in $PublicFolder) { 138 | 139 | $property = [ordered]@{ 140 | FolderPath = $Folder.FolderPath 141 | Name = $Folder.Name 142 | ItemCount = $Folder.ItemCount 143 | CreationTime = $Folder.CreationTime 144 | LastModificationTime = $Folder.LastModificationTime 145 | } 146 | 147 | $folderObject = New-Object -TypeName PSObject -Property $property 148 | 149 | $null = $exportFolders.Add($folderObject) 150 | } 151 | } 152 | } 153 | 154 | # Export to CSV 155 | if(($PublicFolder | Measure-Object).Count -gt 0) { 156 | 157 | $exportFolders | Export-Csv -Path $CsvFilePath -Encoding UTF8 -NoTypeInformation -Force -Delimiter '|' 158 | 159 | $PublicFolderCount = ($PublicFolder | Measure-Object).Count 160 | } 161 | else { 162 | $PublicFolderCount = 0 163 | 164 | Write-Verbose -Message 'Nothing to export.' 165 | } 166 | 167 | if($SendMail) { 168 | 169 | if( $PublicFolderCount -gt 0) { 170 | # new public fodlers found 171 | $html = $exportFolders | ConvertTo-Html -Fragment -PreContent "

New Public Folders

" 172 | } 173 | else { 174 | # no new public folders found 175 | $html = '

No new public folders found.

' 176 | } 177 | 178 | $head = @" 179 | 180 | $($reportTitle) 181 | 182 |

$($reportTitle)

183 |

Public folder timeframe: $($Days) days

184 |

New public folders created: $($PublicFolderCount)

185 |

Excluded from lost of owners: $($ExcludeUser)

186 | "@ 187 | 188 | [string]$htmlreport = ConvertTo-Html -Body $html -Head $head -Title $reportTitle 189 | 190 | Send-Mail -From $MailFrom -To $MailTo -SmtpServer $MailServer -MessageBody $htmlreport -Subject $reportTitle 191 | } 192 | 193 | exit 0 -------------------------------------------------------------------------------- /Exchange Server/Get-NewPublicFolders/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Exchange Server/Get-NewPublicFolders/README.md: -------------------------------------------------------------------------------- 1 | # Get-NewPublicFolders.ps1 2 | 3 | Get all public folders created during the last X days 4 | 5 | ## Description 6 | 7 | This script gathers all public folders created during the last X days and exportes the gathered data to a CSV file. 8 | 9 | ## Parameters 10 | 11 | ### Days 12 | 13 | Number of last X days to filter newly created public folders. Default: 14 14 | 15 | ### Legacy 16 | 17 | Switch to define that you want to query legacy public folders 18 | 19 | ### ServerName 20 | 21 | Name of Exchange server hostingl egacy public folders 22 | 23 | ## Examples 24 | 25 | ``` PowerShell 26 | .\Get-NewPublicFolders.ps1 -Days 31 -ServerName MYPFSERVER01 -Legacy 27 | ``` 28 | 29 | Query legacy public folder server MYPFSERVER01 for all public folders created during the last 31 days 30 | 31 | ``` PowerShell 32 | .\Get-NewPublicFolders.ps1 -Days 31 33 | ``` 34 | 35 | Query modern public folders for all public folders created during the last 31 days 36 | 37 | ## Note 38 | 39 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 40 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 41 | 42 | ## Credits 43 | 44 | Written by: Thomas Stensitzki 45 | 46 | ### Stay connected 47 | 48 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 49 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 50 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 51 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 52 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 53 | 54 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 55 | 56 | * Website: [https://granikos.eu](https://granikos.eu) 57 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 58 | -------------------------------------------------------------------------------- /Exchange Server/Get-NewPublicFolders/styles.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | body { 3 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 4 | font-size: 10pt; 5 | } 6 | h1 { 7 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 8 | font-size: 16px; 9 | } 10 | h2 { 11 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 12 | font-size: 14px; 13 | } 14 | h3 { 15 | font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 16 | font-size: 12px; 17 | } 18 | table { 19 | font: 10pt Verdana, Geneva, Arial, Helvetica, sans-serif; 20 | border: 1px solid black; 21 | border-collapse: 22 | collapse; font-size: 10pt; 23 | } 24 | table th { 25 | border: 1px solid black; 26 | background: #dddddd; 27 | padding: 5px; 28 | color: #000000; 29 | } 30 | table td { 31 | border: 1px solid black; 32 | padding: 5px; 33 | } 34 | table td.pass { 35 | background: #7FFF00; 36 | } 37 | table td.warn { 38 | background: #FFE600; 39 | } 40 | table td.fail { 41 | background: #FF0000; color: #ffffff; 42 | } 43 | table td.info { 44 | background: #85D4FF; 45 | } 46 | .error { 47 | background: #FF0000; color: #ffffff; 48 | } 49 | .danger {background-color: red} 50 | .warn {background-color: yellow} 51 | .ok {background-color: green} -------------------------------------------------------------------------------- /Exchange Server/Get-PublicFolderCustomFormItems/Get-PublicFolderCustomFormItems.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Get public folder items of a specific type and export the results to a CSV file. 4 | 5 | .DESCRIPTION 6 | This script retrieves all public folders under a specified path, counts the items of a specific type in each folder, and exports the results to a CSV file. 7 | It is particularly useful for counting custom forms in public folders in an Exchange environment. 8 | You must have the Exchange Management Shell or the Exchange Online PowerShell module installed to run this script. 9 | 10 | .LINK 11 | https://granikos.eu/go/ZtsJ 12 | 13 | .PARAMETER PublicFolderPath 14 | The path to the public folder from which to retrieve items. 15 | 16 | .PARAMETER ItemType 17 | The type of item to count in the public folders. Default is 'IPM.Post.FORMNAME'. 18 | Adjust this parameter to specify the item type you are interested in. 19 | You can find the form names by using the `Get-PublicFolderItemStatistics` cmdlet. 20 | 21 | .EXAMPLE 22 | .\Get-PublicFolderCustomFormItems.ps1 -PublicFolderPath '\Department\HR' -ItemType 'IPM.Post.FORMNAME' 23 | 24 | Retrieves all public folders under '\Department\HR', counts the items of type 'IPM.Post.FORMNAME' in each folder, and exports the results to a CSV file. Adjust the name of the item type as needed. 25 | #> 26 | 27 | [CmdletBinding()] 28 | param( 29 | [Parameter( 30 | Mandatory=$true, 31 | HelpMessage = "Path to public folder")] 32 | [ValidateNotNull()] 33 | [string]$PublicFolderPath, 34 | [string]$ItemType = 'IPM.Post.FORMNAME' 35 | ) 36 | 37 | $i = 1 38 | $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path 39 | 40 | $publicFolders = Get-PublicFolder $PublicFolderPath -Recurse -ResultSize Unlimited 41 | 42 | # Ensure we have some public folders to work with 43 | $publicFolderCount = ($publicFolders | Measure-Object).Count 44 | 45 | Write-Host ('Fetched {0} public folders' -f $publicFolderCount ) 46 | 47 | # Initialize an array to hold the folder information 48 | $objFolderItems = @() 49 | 50 | if($publicFolderCount -gt 0){ 51 | 52 | foreach($folder in $publicFolders) { 53 | 54 | Write-Progress -Activity ('Working on ({2}/{1}): {0}' -f $folder.Identity, $publicFolderCount, $i ) -PercentComplete (($i / $publicFolderCount) * 100) 55 | 56 | $folderItems = Get-PublicFolderItemStatistics -Identity $folder.Identity 57 | 58 | $folderItemTypeCount = ($folderItems | Group-Object ItemType | Where-Object{$_.name -eq $ItemType}).Count 59 | 60 | $objFolderItems += [PSCustomObject]@{ 61 | FolderPath = $folder.Identity 62 | ItemCount = ($FolderItems |Measure-Object).Count 63 | ItemTypeCount =$folderItemTypeCount 64 | ItemCreationTime = ($folderItems | ?{$_.ItemType -eq $ItemType} | Sort-Object CreationTime -Descending | Select-Object -First 1).CreationTime 65 | ItemLastModificationTime = ($folderItems | ?{$_.ItemType -eq $ItemType} | Sort-Object LastModificationTime -Descending | Select-Object -First 1).LastModificationTime 66 | } 67 | 68 | $i++ 69 | } 70 | 71 | # Output the results to the console 72 | $objFolderItems | Format-Table -autosize 73 | 74 | # Export the results to a CSV file 75 | $objFolderItems | export-csv -Path (Join-Path -Path $scriptDir -ChildPath 'CustomFormsReport.csv') -Encoding UTF8 -NoTypeInformation -Force 76 | 77 | } 78 | else { 79 | Write-Host 'No public folders found.' 80 | } -------------------------------------------------------------------------------- /Exchange Server/Get-PublicFolderCustomFormItems/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Exchange Server/Get-PublicFolderCustomFormItems/README.md: -------------------------------------------------------------------------------- 1 | # Get-PublicFolderCustomFormItems.ps1 2 | 3 | Get public folder items of a specific type and export the results to a CSV file. 4 | 5 | ## Description 6 | 7 | This script retrieves all public folders under a specified path, counts the items of a specific type in each folder, and exports the results to a CSV file. 8 | It is particularly useful for counting custom forms in public folders in an Exchange environment. 9 | You must have the Exchange Management Shell or the Exchange Online PowerShell module installed to run this script. 10 | 11 | ## Parameters 12 | 13 | ### PublicFolderPath 14 | 15 | The path to the public folder from which to retrieve items. 16 | 17 | ### ItemType 18 | 19 | The type of item to count in the public folders. Default is 'IPM.Post.FORMNAME'. 20 | Adjust this parameter to specify the item type you are interested in. 21 | You can find the form names by using the `Get-PublicFolderItemStatistics` cmdlet. 22 | 23 | ## Examples 24 | 25 | ``` PowerShell 26 | .\Get-PublicFolderCustomFormItems.ps1 -PublicFolderPath '\Department\HR' -ItemType 'IPM.Post.FORMNAME' 27 | ``` 28 | 29 | Retrieves all public folders under '\Department\HR', counts the items of type 'IPM.Post.FORMNAME' in each folder, and exports the results to a CSV file. Adjust the name of the item type as needed. 30 | 31 | ## Note 32 | 33 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 34 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 35 | 36 | ## Credits 37 | 38 | Written by: Thomas Stensitzki 39 | 40 | ### Stay connected 41 | 42 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 43 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 44 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 45 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 46 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 47 | 48 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 49 | 50 | * Website: [https://granikos.eu](https://granikos.eu) 51 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 52 | -------------------------------------------------------------------------------- /Exchange Server/Get-RemoteSmtpServers/Get-RemoteSmtpServersTls.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Fetch all remote SMTP servers from Exchange receive connector logs, establishing a TLS connection 4 | 5 | Thomas Stensitzki 6 | 7 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 8 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 9 | 10 | Version 1.0, 2021-06-04 11 | 12 | Ideas, comments and suggestions to support@granikos.eu 13 | 14 | .LINK 15 | http://scripts.granikos.eu 16 | 17 | .DESCRIPTION 18 | This scripts fetches remote SMTP servers by searching the Exchange receive connector logs for successful TLS connections. 19 | Fetched servers can be exported to a single CSV file for all receive connectors across Exchange Servers or 20 | exported to a separate CSV file per Exchange Server. 21 | You can use this script to identify remote servers connecting using TLS 1.0 or TLS 1.1. 22 | 23 | 24 | .NOTES 25 | Requirements 26 | - Exchange Server 2013+ 27 | 28 | Revision History 29 | -------------------------------------------------------------------------------- 30 | 1.0 Initial community release 31 | 32 | .PARAMETER Servers 33 | List of Exchange servers, modern and legacy Exchange servers cannot be mixed 34 | 35 | .PARAMETER Backend 36 | Search backend transport (aka hub transport) log files, instead of frontend transport, which is the default 37 | 38 | .PARAMETER ToCsv 39 | Export search results to a single CSV file for all servers 40 | 41 | .PRAMATER ToCsvPerServer 42 | Export search results to a separate CSV file per servers 43 | 44 | .PARAMETER AddDays 45 | File selection filter, -5 will select log files changed during the last five days. Default: -10 46 | 47 | 48 | .EXAMPLE 49 | .\Get-RemoteSmtpServers.ps1 -Servers SRV01,SRV02 -LegacyExchange -AddDays -4 -ToCsv 50 | 51 | Search legacy Exchange servers SMTP receive log files for the last 4 days and save search results in a single CSV file 52 | 53 | #> 54 | 55 | 56 | [CmdletBinding()] 57 | param( 58 | $Servers = @('MYEXCHANGE'), 59 | [switch]$Backend, 60 | [switch]$ToCsv, 61 | [switch]$ToCsvPerServer, 62 | [int]$AddDays = -10 63 | ) 64 | 65 | $ScriptDir = Split-Path -Path $script:MyInvocation.MyCommand.Path 66 | 67 | $CsvFileName = ('RemoteSMTPServersTls-%SERVER%-%ROLE%-%TLS%-{0}.csv' -f ((Get-Date).ToString('s').Replace(':','-'))) 68 | 69 | # ToDo: Update to Get-TransportServer/Get-TransportService 70 | # Currently pretty static 71 | $BackendPath = '\\%SERVER%\d$\Program Files\Microsoft\Exchange Server\V15\TransportRoles\Logs\Hub\ProtocolLog\SmtpReceive' 72 | $FrontendPath = '\\%SERVER%\d$\Program Files\Microsoft\Exchange Server\V15\TransportRoles\Logs\FrontEnd\ProtocolLog\SmtpReceive' 73 | 74 | # The TLS version to search 75 | $TlsProtocols = @('TLS1_2','TLS1_1','TLS1_0') 76 | 77 | # The SMTP receive log search pattern 78 | $Pattern = '(.)*SP_PROT_%TLS%(.)*succeeded' 79 | 80 | # An empty array for storing remote servers 81 | $RemoteServers = @() 82 | 83 | # Create an empty array 84 | $RemoteServersOutput = @() 85 | 86 | function Write-RemoteServers { 87 | [CmdletBinding()] 88 | param( 89 | [string]$FilePath = '' 90 | ) 91 | 92 | # sort servers 93 | $RemoteServers = $RemoteServers | Select-Object -Unique | Sort-Object 94 | 95 | if(($RemoteServers| Measure-Object).Count -gt 0) { 96 | 97 | # Create an empty array 98 | $RemoteServersOutput = @() 99 | 100 | foreach($Server in $RemoteServers) { 101 | 102 | if($Server.Trim() -ne '') { 103 | $obj = New-Object -TypeName PSObject 104 | $obj | Add-Member -MemberType NoteProperty -Name 'Remote Server' -Value $Server 105 | $RemoteServersOutput += $obj 106 | } 107 | } 108 | 109 | if($ToCsv -or $ToCsvPerServer) { 110 | # save remote servers list as csv 111 | $null = $RemoteServersOutput | Export-Csv -Path $FilePath -Encoding UTF8 -NoTypeInformation -Force -Confirm:$false 112 | 113 | Write-Verbose -Message ('Remote server list written to: {0}' -f $FilePath) 114 | } 115 | 116 | $RemoteServersOutput 117 | 118 | } 119 | else { 120 | Write-Host 'No remote servers found!' 121 | } 122 | 123 | } 124 | 125 | ## MAIN ########################################### 126 | $LogPath = $FrontendPath 127 | 128 | # Adjust CSV file name to reflect either HUB or FRONTEND transport 129 | if($Backend) { 130 | $LogPath = $BackendPath 131 | $CsvFileName = $CsvFileName.Replace('%ROLE%','HUB') 132 | } 133 | else { 134 | $CsvFileName = $CsvFileName.Replace('%ROLE%','FE') 135 | } 136 | 137 | Write-Verbose -Message ('CsvFileName: {0}' -f ($CsvFileName)) 138 | 139 | # Fetch each Exchange Server server 140 | foreach($Server in $Servers) { 141 | 142 | $Server = $Server.ToUpper() 143 | 144 | $Path = $LogPath.Replace('%SERVER%', $Server) 145 | 146 | Write-Verbose -Message ('Working on Server {0} | {1}' -f $Server, $Path) 147 | 148 | # fetching log files requires an account w/ administrative access to the target server 149 | $LogFiles = Get-ChildItem -Path $Path -File | Where-Object {$_.LastWriteTime -gt (Get-Date).AddDays($AddDays)} | Select-Object -First 2 150 | 151 | $LogFileCount = ($LogFiles | Measure-Object).Count 152 | 153 | foreach($Tls in $TlsProtocols) { 154 | Write-Host ('Working on: {0}' -f $Tls) 155 | $FileCount = 1 156 | 157 | foreach($File in $LogFiles) { 158 | 159 | Write-Progress -Activity ('{3} | {4} | File [{0}/{1}] : {2}' -f $FileCount, $LogFileCount, $File.Name, $Server, $Tls) -PercentComplete(($FileCount/$LogFileCount)*100) 160 | 161 | 162 | # find results in selected log files 163 | $SearchPattern = $Pattern.Replace('%TLS%', $Tls) 164 | 165 | $results = (Select-String -Path $File.FullName -Pattern $SearchPattern) 166 | 167 | Write-Verbose -Message ('Results {0} : {1}' -f $File.FullName, ($results | Measure-Object).Count) 168 | 169 | # Get remote server information from search string result 170 | foreach($record in $results) { 171 | 172 | # Fetch remote hostname 173 | # $HostName = ($record.line -replace $Pattern,'').Replace(',','').Trim().ToUpper() 174 | $HostIp = (($record.Line).Split(',')[5]).Split(':')[0] 175 | 176 | # Try to resolve remote IP address as the line does not contain a server name 177 | $HostName = Resolve-DnsName $HostIp -ErrorAction Ignore |Select-Object -ExpandProperty NameHost 178 | 179 | if(-not $RemoteServers.Contains($HostName)) { 180 | $RemoteServers += $HostName 181 | } 182 | } 183 | 184 | $FileCount++ 185 | 186 | } 187 | 188 | if($ToCsvPerServer) { 189 | 190 | $CsvFile = $CsvFileName.Replace('%SERVER%',$Server).Replace('%TLS%',$Tls) 191 | 192 | Write-Verbose -Message $CsvFile 193 | 194 | Write-RemoteServers -FilePath $CsvFile 195 | 196 | $RemoteServers = @() 197 | } 198 | } 199 | } 200 | 201 | if($ToCsv) { 202 | $CsvFile = $CsvFileName.Replace('%SERVER%','ALL') 203 | 204 | Write-Verbose -Message $CsvFile 205 | 206 | Write-RemoteServers -FilePath $CsvFile 207 | } -------------------------------------------------------------------------------- /Exchange Server/Get-RemoteSmtpServers/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Exchange Server/Get-RemoteSmtpServers/README.md: -------------------------------------------------------------------------------- 1 | # Get-RemoteSmtpServers.ps1 2 | 3 | Fetch all remote SMTP servers from Exchange receive connector logs 4 | 5 | ## Description 6 | 7 | This scripts fetches remote SMTP servers by searching the Exchange receive connector logs for the EHLO string. 8 | Fetched remote servers can be exported to a single CSV file for all receive connectors across Exchange Servers or exported to a separate CSV file per Exchange Server. 9 | 10 | ## Requirements 11 | 12 | - Exchange Server 2010, Exchange Server 2013+ 13 | 14 | ## Parameters 15 | 16 | ### Servers 17 | 18 | List of Exchange servers, modern and legacy Exchange servers cannot be mixed 19 | 20 | ### ServersToExclude 21 | 22 | List of host names that you want to exclude from the outout 23 | 24 | ### Backend 25 | 26 | Search backend transport (aka hub transport) log files, instead of frontend transport, which is the default 27 | 28 | ### LegacyExchange 29 | 30 | Search legacy Exchange servers (Exchange 2010) log file location 31 | 32 | ### ToCsv 33 | 34 | Export search results to a single CSV file for all servers 35 | 36 | ### ToCsvPerServer 37 | 38 | Export search results to a separate CSV file per servers 39 | 40 | ### UniqueIPs 41 | 42 | Simplify the out list by reducing the output to unique IP address 43 | 44 | ### AddDays 45 | 46 | File selection filter, -5 will select log files changed during the last five days. Default: -10 47 | 48 | ## Examples 49 | 50 | ``` PowerShell 51 | .\Get-RemoteSmtpServers.ps1 -Servers SRV01,SRV02 -LegacyExchange -AddDays -4 -ToCsv 52 | ``` 53 | 54 | Search legacy Exchange servers SMTP receive log files for the last 4 days and save search results in a single CSV file 55 | 56 | ``` PowerShell 57 | .\Get-RemoteSmtpServers.ps1 -Servers SRV03,SRV04 -AddDays -4 -ToCsv -UniqueIPs 58 | ``` 59 | 60 | Search Exchange servers SMTP receive log files for the last 4 days and save search results in a single CSV file, with unique IP addresses only 61 | 62 | 63 | ## Note 64 | 65 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 66 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 67 | 68 | ## Credits 69 | 70 | Written by: Thomas Stensitzki | MVP 71 | 72 | Related blog post: [https://granikos.eu/fetch-remote-smtp-servers-connecting-to-exchange/](https://granikos.eu/fetch-remote-smtp-servers-connecting-to-exchange/) 73 | 74 | ### Stay connected 75 | 76 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 77 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 78 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 79 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 80 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 81 | 82 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 83 | 84 | * Website: [https://granikos.eu](https://granikos.eu) 85 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 86 | -------------------------------------------------------------------------------- /Exchange Server/Import-EdgeSubscription/Import-EdgeSubscription.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script imports an Edge Subscription file for a specific Active Directory site. 4 | 5 | Thomas Stensitzki 6 | 7 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 8 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 9 | 10 | Version 1.0, 2024-02-15 11 | 12 | Please send ideas, comments and suggestions to support@granikos.eu 13 | 14 | .LINK 15 | https://scripts.granikos.eu 16 | 17 | .DESCRIPTION 18 | The script takes two parameters: the path to the Edge Subscription file and the name of the Active Directory site. 19 | It then imports the Edge Subscription file for the specified Active Directory site and does not create an Internet send connector. 20 | This script is primarily used for hybrid Exchange deployments where a different internet send connector already exists. 21 | 22 | .PARAMETER edgeSubscriptionFile 23 | The full path to the Edge Subscription file. 24 | 25 | .PARAMETER activeDirectorySite 26 | The name of the Active Directory site for subscribign the Edge Transport Server to. 27 | 28 | .EXAMPLE 29 | .\Import-EdgeSubscription.ps1 -edgeSubscriptionFile "C:\Import\EdgeSubscription.xml" -activeDirectorySite "ADSiteName" 30 | 31 | #> 32 | param( 33 | [string]$edgeSubscriptionFile = "C:\Import\EdgeSubscription.xml", 34 | [string]$activeDirectorySite = "ADSiteName" 35 | ) 36 | 37 | Import-EdgeSubscription -FileData ([byte[]]$(Get-Content -Path $edgeSubscriptionFile -Encoding Byte -ReadCount 0)) -Site $activeDirectorySite -CreateInternetSendConnector $false 38 | 39 | Write-Output @( 40 | ('Imported Edge Subscription from {0} for Active Directory site {1}' -f $edgeSubscriptionFile, $activeDirectorySite), 41 | 'One of the Exchange Servers in the Active Directory site will use the imported BootStrap account to initiate the Edge Subscription process.', 42 | 'Use Test-EdgeSynchronization to verify that Exchange mailbox servers communicate with subscribed Edge Transport Server.', 43 | 'More information: https://learn.microsoft.com/powershell/module/exchange/test-edgesynchronization?view=exchange-ps' 44 | ) 45 | 46 | 47 | -------------------------------------------------------------------------------- /Exchange Server/Import-EdgeSubscription/README.md: -------------------------------------------------------------------------------- 1 | # Import-EdgeSubscription.ps1 2 | 3 | This script imports an Edge Subscription file for a specific Active Directory site. 4 | 5 | ## Description 6 | 7 | The script takes two parameters: the path to the Edge Subscription file and the name of the Active Directory site. 8 | It then imports the Edge Subscription file for the specified Active Directory site and does not create an Internet send connector. 9 | This script is primarily used for hybrid Exchange deployments where a different internet send connector already exists. 10 | 11 | ## Parameters 12 | 13 | ### edgeSubscriptionFile 14 | 15 | The full path to the Edge Subscription file. 16 | 17 | ### activeDirectorySite 18 | 19 | The name of the Active Directory site for subscribign the Edge Transport Server to. 20 | 21 | ## Example 22 | 23 | ``` PowerShell 24 | .\Import-EdgeSubscription.ps1 -edgeSubscriptionFile "C:\Import\EdgeSubscription.xml" -activeDirectorySite "Munich" 25 | ``` 26 | Import an Edge Transport subscription to an Exchange organization to Active Directory site "Munich" 27 | 28 | ## Credits 29 | 30 | Written by: Thomas Stensitzki 31 | 32 | ### Stay connected 33 | 34 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 35 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 36 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 37 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 38 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 39 | 40 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 41 | 42 | * Website: [https://granikos.eu](https://granikos.eu) 43 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 44 | -------------------------------------------------------------------------------- /Exchange Server/New-RoomMailbox/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Exchange Server/New-RoomMailbox/README.md: -------------------------------------------------------------------------------- 1 | # New-RoomMailbox.ps1 2 | 3 | Creates a new room mailbox, security groups for full access and send-as permission and adds the security groups to the room mailbox configuration. 4 | 5 | ## Description 6 | 7 | This scripts creates a new room mailbox and additonal security groups for full access and and send-as delegation. The security groups are created using a confgurable naming convention. 8 | You can add existing groups to the full access and send-as to a room mailbox. This is useful, if you have a room management department and want to grant permissions. 9 | The script adds the room mailbox to an existing room list, if configured. 10 | 11 | ## Parameters 12 | 13 | ### RoomMailboxName 14 | 15 | Name attribute of the new room mailbox 16 | 17 | ### RoomMailboxDisplayName 18 | 19 | Display name attribute of the new room mailbox 20 | 21 | ### RoomMailboxAlias 22 | 23 | Alias attribute of the new room mailbox 24 | 25 | ### RoomMailboxSmtpAddress 26 | 27 | Primary SMTP address attribute the new room mailbox 28 | 29 | ### DepartmentPrefix 30 | 31 | Department prefix for automatically generated security groups (optional) 32 | 33 | ### GroupFullAccessMembers 34 | 35 | String array containing full access members 36 | 37 | ### GroupSendAsMembers 38 | 39 | String array containing send as members 40 | 41 | ### GroupCalendarBookingMembers 42 | 43 | String array containing users having calendar booking rights 44 | 45 | ### RoomPhoneNumber 46 | 47 | Phone number of a phone located in the room, this value will show in the Outlook room list 48 | 49 | ### RoomList 50 | 51 | Add the new room mailbox to this existing room list 52 | 53 | ### AutoAccept 54 | 55 | Set room mailbox to automatically accept booking requests 56 | 57 | ### Language 58 | 59 | Locale setting for calendar regional configuration language, e.g., de-DE, en-US 60 | 61 | ## Examples 62 | 63 | ``` PowerShell 64 | .\New-RoomMailbox.ps1 -RoomMailboxName "MB - Conference Room" -RoomMailboxDisplayName "Board Conference Room" -RoomMailboxAlias "MB-ConferenceRoom" -RoomMailboxSmtpAddress "ConferenceRoom@mcsmemail.de" -DepartmentPrefix "C" 65 | ``` 66 | Create a new room mailbox, empty full access and empty send-as security groups 67 | 68 | ## Credits 69 | 70 | Written by: Thomas Stensitzki 71 | 72 | ### Stay connected 73 | 74 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 75 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 76 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 77 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 78 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 79 | 80 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 81 | 82 | * Website: [https://granikos.eu](https://granikos.eu) 83 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 84 | -------------------------------------------------------------------------------- /Exchange Server/New-RoomMailbox/Run-NewRoomMailbox.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Run-NewRoomMailbox.ps1 3 | 4 | Helper script to simplify the use of New-RoomMailbox.ps1 5 | #> 6 | 7 | $roomMailboxName = 'MB-Conference Room' 8 | $roomMailboxDisplayName = 'Board Conference Room' 9 | $roomMailboxAlias = 'MB-ConferenceRoom' 10 | $roomMailboxSmtpAddress = 'ConferenceRoom@mcsmemail.de' 11 | $departmentPrefix = 'C' 12 | $groupFullAccessMembers = @('JohnDoe','JaneDoe') # Empty = @() 13 | $groupSendAsMembers = @() 14 | $groupCalendarBookingMembers = @() 15 | $RoomCapacity = 0 16 | $RoomList = 'AllRoomsHQ' 17 | $Language = 'de-DE' # en-US, de-DE, fr-FR, ja-JP, pt-BR, zh-CN, zh-TW, ... 18 | 19 | .\New-RoomMailbox.ps1 -RoomMailboxName $roomMailboxName -RoomMailboxDisplayName $roomMailboxDisplayName -RoomMailboxAlias $roomMailboxAlias -RoomMailboxSmtpAddress $roomMailboxSmtpAddress -DepartmentPrefix $departmentPrefix -GroupFullAccessMembers $groupFullAccessMembers -GroupSendAsMembers $groupSendAsMembers -RoomCapacity $RoomCapacity -AutoAccept -RoomList $RoomList -Language $Language 20 | 21 | if ($roomMailboxSmtpAddress -ne '') { 22 | # Use the provided room mailbox SMTP address 23 | .\New-RoomMailbox.ps1 -RoomMailboxName $roomMailboxName -RoomMailboxDisplayName $roomMailboxDisplayName -RoomMailboxAlias $roomMailboxAlias -RoomMailboxSmtpAddress $roomMailboxSmtpAddress -GroupFullAccessMembers $groupFullAccessMembers -GroupSendAsMember $groupSendAsMembers -RoomCapacity $RoomCapacity -AutoAccept -RoomList $RoomList -Language $Language 24 | } 25 | else { 26 | # Generate the room mailbox SMTP address automatically 27 | .\New-RoomMailbox.ps1 -roomMailboxName $roomMailboxName -RoomMailboxDisplayName $roomMailboxDisplayName -roomMailboxAlias $roomMailboxAlias -GroupFullAccessMembers $groupFullAccessMembers -GroupSendAsMember $groupSendAsMembers -RoomCapacity $RoomCapacity -AutoAccept -RoomList $RoomList -Language $Language 28 | } -------------------------------------------------------------------------------- /Exchange Server/New-RoomMailbox/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | pre_ 5 | _SA 6 | _FA 7 | _CB 8 | mcsmemail.de/IT/Groups/Mail/Rooms 9 | mcsmemail.de 10 | - 11 | 12 | 13 | mcsmemail.de/IT/Mail/RoomMailboxes 14 | 15 | 16 | 10 17 | 18 | 19 | 20 | 21 | 22 | AADSync 23 | 24 | -------------------------------------------------------------------------------- /Exchange Server/New-TeamMailbox/Create-TeamMailbox.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Create-TeamMailbox.ps1 3 | 4 | Helper script to simplify the use of New-TeamMailbox.ps1 5 | #> 6 | 7 | $teamMailboxName = 'TM-Exchange Admin' 8 | $teamMailboxDisplayName = 'Exchange Admins' 9 | $teamMailboxAlias = 'TM-ExchangeAdmin' 10 | $teamMailboxSmtpAddress = 'ExchangeAdmins@mcsmemails.de' 11 | $departmentPrefix = 'IT' 12 | $groupFullAccessMembers = @('exAdmin1','exAdmin2') 13 | $groupSendAsMember = @('exAdmin1','exAdmin2') 14 | 15 | .\New-TeamMailbox.ps1 -TeamMailboxName $teamMailboxName -TeamMailboxDisplayName $teamMailboxDisplayName -TeamMailboxAlias $teamMailboxAlias -TeamMailboxSmtpAddress $teamMailboxSmtpAddress -DepartmentPrefix $departmentPrefix -GroupFullAccessMembers $groupFullAccessMembers -GroupSendAsMember $groupSendAsMember -Verbose -------------------------------------------------------------------------------- /Exchange Server/New-TeamMailbox/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Exchange Server/New-TeamMailbox/README.md: -------------------------------------------------------------------------------- 1 | # New-TeamMailbox.ps1 2 | 3 | Creates a new shared mailbox, security groups for full access and send-as permission and adds the security groups to the shared mailbox configuration. 4 | 5 | ## Description 6 | 7 | This scripts creates a new shared mailbox (aka team mailbox) and security groups for full access and and send-as delegation. Security groups are created using a naming convention. 8 | 9 | Starting with v1.4 the script sets the sAMAccountName of the security groups to the group name to avoid numbered name extension of sAMAccountName. 10 | 11 | ## Parameters 12 | 13 | ### TeamMailboxName 14 | 15 | Name attribute of the new team mailbox 16 | 17 | ### TeamMailboxDisplayName 18 | 19 | Display name attribute of the new team mailbox 20 | 21 | ### TeamMailboxAlias 22 | 23 | Alias attribute of the new team mailbox 24 | 25 | ### TeamMailboxSmtpAddress 26 | 27 | Primary SMTP address attribute the new team mailbox 28 | 29 | ### DepartmentPrefix 30 | 31 | Department prefix for automatically generated security groups (optional) 32 | 33 | ### GroupFullAccessMembers 34 | 35 | String array containing full access members 36 | 37 | ### GroupFullAccessMembers 38 | 39 | String array containing send as members 40 | 41 | ## Examples 42 | 43 | ``` PowerShell 44 | .\New-TeamMailbox.ps1 -TeamMailboxName "TM-Exchange Admins" -TeamMailboxDisplayName "Exchange Admins" -TeamMailboxAlias "TM-ExchangeAdmins" -TeamMailboxSmtpAddress "ExchangeAdmins@mcsmemail.de" -DepartmentPrefix "IT" 45 | ``` 46 | 47 | Create a new team mailbox, empty full access and empty send-as security groups 48 | 49 | ## Note 50 | 51 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 52 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 53 | 54 | ## Credits 55 | 56 | Written by: Thomas Stensitzki 57 | 58 | ### Stay connected 59 | 60 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 61 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 62 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 63 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 64 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 65 | 66 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 67 | 68 | * Website: [https://granikos.eu](https://granikos.eu) 69 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 70 | -------------------------------------------------------------------------------- /Exchange Server/New-TeamMailbox/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | pre_ 5 | _SA 6 | _FA 7 | _CB 8 | mcsmemail.de/IT/Groups/Mail 9 | mcsmemail.de 10 | - 11 | 12 | 13 | mcsmemail.de/IT/SharedMailboxes 14 | 15 | 16 | 60 17 | TENANT.mail.onmicrosoft.com 18 | 19 | 20 | AADSync 21 | 22 | -------------------------------------------------------------------------------- /Exchange Server/Purge-LogFiles/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Exchange Server/Purge-LogFiles/README.md: -------------------------------------------------------------------------------- 1 | # IMPORTANT NOTE 2 | 3 | Update to most current release if you are using _v2.1_ 4 | 5 | # Purge-LogFiles.ps1 6 | 7 | PowerShell script for modern Exchange Server environments to clean up Exchange Server and IIS log files. 8 | 9 | ## Description 10 | 11 | This script deletes all Exchange Server and IIS logs older than X days from all Exchange 2013+ servers that are fetched using the Get-ExchangeServer cmdlet. 12 | 13 | The Exchange log file location is read from a variable and used to build an administrative UNC path for file deletions. It is assumed that the Exchange setup path is IDENTICAL across all Exchange servers. 14 | 15 | Optionally, you can use the Active Directory configuration partition to determine the Exchange install path dynamically, if supported in your Active Directory environment. 16 | 17 | The IIS log file location is read from the local IIS metabase of the LOCAL server and is used to build an administrative UNC path for IIS log file deletions. 18 | 19 | Currently, it is assumed that the IIS log file location is identical across all Exchange servers. 20 | 21 | ## Requirements 22 | 23 | - Windows Server 2012 R2 or newer 24 | - Utilizes the global function library found here: [http://scripts.granikos.eu](http://scripts.granikos.eu) 25 | - AciveDirectory PowerShell module 26 | - Exchange Server 2013+ 27 | - Exchange Management Shell (EMS) 28 | 29 | ## Updates 30 | 31 | - 2020-05-14, v2.3.1, Issues #14, #15 fixed to properly support Edge Transport Role 32 | - 2020-03-12, v2.3, Option for HTTPERR added, Option for dynamic Exchange install paths added, Html formatting added, tested with Exchange Server 2019 33 | 34 | ## Parameters 35 | 36 | ### DaysToKeep 37 | 38 | Number of days Exchange and IIS log files should be retained, default is 30 days 39 | 40 | ### Auto 41 | 42 | Switch to use automatic detection of the IIS and Exchange log folder paths 43 | 44 | ### IsEdge 45 | 46 | Indicates the the script is executed on an Exchange Server holding the EDGE role. Without the switch servers holding the EDGE role are excluded. 47 | 48 | ### IncludeHttpErr 49 | 50 | Include the HTTPERR log files in the purge routine. Those logs are normally stored at _C:\Windows\System32\LogFiles\HTTPERR_. 51 | 52 | ### UseDynamicExchangePaths 53 | 54 | Determine the Exchange install path by querying the server object in AD configuration partition. This helps if your Exchange servers do not have a unified install path across all servers. 55 | 56 | ### RepositoryRootPath 57 | 58 | Absolute path to a repository folder for storing copied log files and compressed archives. Preferably an UNC path. A new subfolder will be created for each Exchange server. 59 | 60 | ### ArchiveMode 61 | 62 | Log file copy and archive mode. Possible values 63 | 64 | - _None_ = All log files will be purged without being copied 65 | - _CopyOnly_ = Simply copy log files to the RepositoryRootPath 66 | - _CopyAndZip_ = Copy logfiles and send copied files to compressed archive 67 | - _CopyZipAndDelete_ = Same as CopyAndZip, but delete copied log files from RepositoryRootPath 68 | 69 | ### SendMail 70 | 71 | Switch to send an Html report 72 | 73 | ### MailFrom 74 | 75 | Email address of report sender 76 | 77 | ### MailTo 78 | 79 | Email address of report recipient 80 | 81 | ### MailServer 82 | 83 | SMTP Server for email report 84 | 85 | ## Examples 86 | 87 | ``` PowerShell 88 | .\Purge-LogFiles -DaysToKeep 14 89 | ``` 90 | 91 | Delete Exchange and IIS log files older than 14 days 92 | 93 | ``` PowerShell 94 | .\Purge-LogFiles -DaysToKeep 7 -Auto 95 | ``` 96 | 97 | Delete Exchange and IIS log files older than 7 days with automatic discovery 98 | 99 | ``` PowerShell 100 | .\Purge-LogFiles -DaysToKeep 7 -Auto -SendMail -MailFrom postmaster@sedna-inc.com -MailTo exchangeadmin@sedna-inc.com -MailServer mail.sedna-inc.com 101 | ``` 102 | 103 | Delete Exchange and IIS log files older than 7 days with automatic discovery and send email report 104 | 105 | ``` PowerShell 106 | .\Purge-LogFiles -DaysToKeep 14 -RepositoryRootPath \\OTHERSERVER\OtherShare\LOGS -ArchiveMode CopyZipAndDelete` 107 | ``` 108 | 109 | Delete Exchange and IIS log files older than 14 days, but copy files to a central repository and compress the log files before final deletion 110 | 111 | ``` PowerShell 112 | .\Purge-LogFiles.ps1 -DaysToKeep 7 -SendMail -MailFrom postmaster@sedna-inc.com -MailTo exchangeadmin@sedna-inc.com -MailServer mail.sedna-inc.com -UseDynamicExchangePaths -IncludeHttpErr 113 | ``` 114 | 115 | Delete Exchange Server, IIS, and HTTPERR log files older than 7 days, and send an HTML email. Identify Exchange file paths using AD configuration objects. 116 | 117 | ## Note 118 | 119 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 120 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 121 | 122 | ## Credits 123 | 124 | Written by: Thomas Stensitzki 125 | 126 | ### Stay connected 127 | 128 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 129 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 130 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 131 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 132 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 133 | 134 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 135 | 136 | * Website: [https://granikos.eu](https://granikos.eu) 137 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 138 | 139 | ### Additional Credits 140 | 141 | - Is-Admin function (c) by Michel de Rooij, michel@eightwone.com -------------------------------------------------------------------------------- /Exchange Server/Purge-LogFiles/Run-PurgeLogFiles.ps1: -------------------------------------------------------------------------------- 1 | # Example file to help execute Purge-LogFiles.ps1 with a single PowerShell script, e.g. used for Scheduled Tasks 2 | 3 | .\Purge-LogFiles.ps1 -DaysToKeep 1 -SendMail -MailFrom postmaster@mcsmemail.de -MailTo it@mcsmemail.de -MailServer mail.mcsmemail.de -------------------------------------------------------------------------------- /Exchange Server/README.md: -------------------------------------------------------------------------------- 1 | # Exchange Server PowerShell Scripts 2 | 3 | This GitHub Repository contains most of my public PowerShell scripts for modern Exchange Server versions (2013, 2016, 2019). 4 | 5 | Please refer to the table of content in the main [README](https://github.com/Apoc70/PowerShell-Scripts) file. 6 | 7 | ### Stay connected 8 | 9 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 10 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 11 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 12 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 13 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 14 | 15 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 16 | 17 | * Website: [https://granikos.eu](https://granikos.eu) 18 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 19 | -------------------------------------------------------------------------------- /Exchange Server/Remove-ProxyAddress/Remove-ProxyAddress.ps1: -------------------------------------------------------------------------------- 1 | # Description: Remove email addresses from users in Active Directory 2 | 3 | $DomainFilter = "*mail.onmicrosoft.com" 4 | 5 | # Get all users with email addresses in scope 6 | Get-ADUser -Properties proxyaddresses -Filter {ProxyAddresses -like $DomainFilter} | 7 | ForEach-Object { 8 | # Remove the email addresses 9 | ForEach ($proxyAddress in $_.proxyAddresses) { 10 | # Check if the email address is in scope 11 | If ($proxyAddress -like $DomainFilter) { 12 | # Output the action to the console 13 | Write-Verbose -Message ('Removing $proxyAddress from {0}' -f $_.SamAccountName) 14 | # Remove the email address 15 | Set-ADUser $_.SamAccountName -Remove @{ProxyAddresses=$proxyAddress} 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Exchange Server/Set-EdgeDNSSuffix/Set-EdgeDNSSuffix.ps1: -------------------------------------------------------------------------------- 1 | [cmdletbinding()] 2 | param() 3 | 4 | # DNS Suffix for Edge Transport Server 5 | $DNSSuffix = "varunagroup.de" 6 | 7 | # Fetch current DNS Suffix 8 | $keyPath = 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\' 9 | $valueName = 'NV Domain' 10 | 11 | try{ 12 | # $oldDNSSuffix = Get-ItemProperty -Path $keyPath -Name $valueName -ErrorAction Stop 13 | $oldDNSSuffix = (Get-ItemProperty $keyPath -Name $valueName -ErrorAction Stop).$valueName 14 | 15 | if($oldDNSSuffix -eq '') { 16 | 17 | #Update primary DNS Suffix for FQDN 18 | Write-Verbose -Message ('Adding {0} to {1}' -f $DNSSuffix, $keyPath) 19 | Set-ItemProperty $keyPath -Name 'Domain' -Value $DNSSuffix 20 | Set-ItemProperty $keyPath -Name $valueName -Value $DNSSuffix 21 | 22 | # Update DNS Suffix Search List - Win8/2012 and above - if needed 23 | # Set-DnsClientGlobalSetting -SuffixSearchList $oldDNSSuffix,$DNSSuffix 24 | } 25 | else { 26 | Write-Host ("The computer DNS suffix is already set to {0}.`nThe script did not change the current configuration." -f $oldDNSSuffix) 27 | } 28 | 29 | } 30 | catch { 31 | $null = New-ItemProperty -Path $keyPath -Name $valueName -Value $DNSSuffix -Force 32 | $null = New-ItemProperty -Path $keyPath -Name 'Domain' -Value $DNSSuffix -Force 33 | Write-Host ("The computer DNS suffix is now set to {0}.`nPlease restart the computer." -f $DNSSuffix) 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /Exchange Server/Set-ExchangePreferredRegistrySettings/Disable-IPv6-Correctly.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apoc70/PowerShell-Scripts/ef43113b389502d309e246891fa9c7a3ad5a147e/Exchange Server/Set-ExchangePreferredRegistrySettings/Disable-IPv6-Correctly.reg -------------------------------------------------------------------------------- /Exchange Server/Set-ExchangePreferredRegistrySettings/RPC-TimeOut-PortScaling.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apoc70/PowerShell-Scripts/ef43113b389502d309e246891fa9c7a3ad5a147e/Exchange Server/Set-ExchangePreferredRegistrySettings/RPC-TimeOut-PortScaling.reg -------------------------------------------------------------------------------- /Exchange Server/Set-ExchangePreferredRegistrySettings/Set-ExchangePreferredRegistrySettings.ps1: -------------------------------------------------------------------------------- 1 | write-host-Message "Setting preferred registry settings for Exchange Server 2016/2019" 2 | 3 | # Set KeepAliveTime to 2 hours 4 | New-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters' -Name 'KeepAliveTime' -Value '001b7740' -PropertyType 'DWord' -Force | Out-Null 5 | 6 | # Set KeepAliveInterval to 1 second 7 | New-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Rpc' -Name 'EnableTcpPortScaling' -Value '00000001' -PropertyType 'DWord' -Force | Out-Null 8 | 9 | # Set MinimumConnectionTimeout to 120 seconds 10 | New-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Rpc' -Name 'MinimumConnectionTimeout' -Value '00000078' -PropertyType 'DWord' -Force | Out-Null 11 | 12 | # Set IVv6 to disabled 13 | New-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters' -Name 'DisabledComponents' -Value '000000ff' -PropertyType 'DWord' -Force | Out-Null 14 | 15 | Write-Host 'Preferred registry settings have been applied. PLease restart the server to apply changes.' -------------------------------------------------------------------------------- /Exchange Server/Set-ExchangePreferredRegistrySettings/Tcpip-KeepAliveTime.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apoc70/PowerShell-Scripts/ef43113b389502d309e246891fa9c7a3ad5a147e/Exchange Server/Set-ExchangePreferredRegistrySettings/Tcpip-KeepAliveTime.reg -------------------------------------------------------------------------------- /Exchange Server/Set-UserPicture/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Exchange Server/Set-UserPicture/README.md: -------------------------------------------------------------------------------- 1 | # Set-UserPicture 2 | 3 | This script fetches images from a source folder, resizes images for use with Exchange, Active Directory and Intranet. Resized images are are written to Exchange and Active Directory. 4 | 5 | ## Description 6 | 7 | The script parses all images provided in a dedicated source folder. The images are resized and cropped for use with the following targets: 8 | 9 | * Exchange Server user photo 10 | * Active Directory thumbnailPhoto attribute 11 | * Local intranet use in your local infrastructure 12 | 13 | Source images must be named using the respective user logon name. 14 | 15 | Example: MYDOMAIN\JohnDoe --> JohnDoe.jpg 16 | 17 | Preferably, the images are stored in jpg format. 18 | 19 | Optionally, processed image files are moved from the respective folder (Exchange, AD, Intranet) to a processed folder. 20 | 21 | ## Requirements 22 | 23 | * GlobalFunctions PowerShell module, described here: [https://scripts.granikos.eu](https://scripts.granikos.eu) 24 | * ResizeImage.exe executable, described here: [https://granikos.eu/add-resized-user-photos-automatically/](https://granikos.eu/add-resized-user-photos-automatically/) 25 | * Exchange Server 2016+ Management Shell (EMS) for storing user photos in on-premises mailboxes 26 | * Exchange Online Management Shell for storing user photos in cloud mailboxes 27 | * Write access to thumbnailPhoto attribute in Active Directory 28 | 29 | ## Parameters 30 | 31 | ### PictureSource 32 | 33 | Absolute path to source images 34 | Filenames must match the logon name of the user 35 | 36 | ### TargetPathAD 37 | 38 | Absolute path to store images resized for Active Directory 39 | 40 | ### TargetPathExchange 41 | 42 | Absolute path to store images resized for Exchange 43 | 44 | ### TargetPathIntranet 45 | 46 | Absolute path to store images resized for Intranet 47 | 48 | ### ExchangeOnPrem 49 | 50 | Switch to create resized images for Exchange On-Premesis and store images in users mailbox 51 | Requires the image tool to be available in TargetPathExchange 52 | 53 | ### ExchangeOnline 54 | 55 | Switch to create resized images for Exchange Online and store images in users mailbox 56 | Requires the image tool to be available in TargetPathExchange 57 | 58 | ### ExchangeOnPremisesFqdn 59 | 60 | Name the on-premises Exchange Server Fqdn to connect to using remote PowerShell 61 | 62 | ### ActiveDirectory 63 | 64 | Switch to create resized images for Active Directory and store images in users thumbnailPhoto attribute 65 | Requires the image tool to be available in TargetPathAD 66 | 67 | ### Intranet 68 | 69 | Switch to create resized images for Intranet 70 | Requires the image tool to be available in TargetPathIntranet 71 | 72 | ### SaveUserStatus 73 | 74 | Switch to save a last modified status in a local Xml file. Currently in development. 75 | 76 | ### MoveAction 77 | 78 | Optional action to move processed images to a dedicated sub folder. 79 | 80 | Possible values: 81 | 82 | * MoveTargetToProcessed = Move Exchange, AD or Intranet pictures to a subfolder 83 | * MoveSourceToProcessed = Move image source to a subfolder 84 | 85 | ## Examples 86 | 87 | ``` PowerShell 88 | .\Set-UserPicture.ps1 -ExchangeOnPrem 89 | ``` 90 | 91 | Resize photos stored in the default PictureSource folder for Exchange (648x648) and write images to user mailboxes 92 | 93 | ``` PowerShell 94 | .\Set-UserPicture.ps1 -ExchangeOnline -PictureSource '\\SRV01\HRShare\Photos' -TargetPathExchange '\\SRV02\ExScripts\Photos' 95 | ``` 96 | 97 | Resize photos stored on a SRV01 share for Exchange Online and save resized photos on a SRV02 share 98 | 99 | ``` PowerShell 100 | .\Set-UserPicture.ps1 -ActiveDirectory 101 | ``` 102 | 103 | Resize photos stored in the default PictureSource folder for Active Directory (96x96) and write images to user thumbnailPhoto attribute 104 | 105 | ``` PowerShell 106 | .\Set-UserPicture.ps1 -Intranet 107 | ``` 108 | 109 | Resize photos stored in the default PictureSource folder for Intranet (150x150) 110 | 111 | ## Note 112 | 113 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 114 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 115 | 116 | ## Credits 117 | 118 | Written by: Thomas Stensitzki 119 | 120 | ### Stay connected 121 | 122 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 123 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 124 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 125 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 126 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 127 | 128 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 129 | 130 | * Website: [https://granikos.eu](https://granikos.eu) 131 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 132 | -------------------------------------------------------------------------------- /Exchange Server/Set-UserPicture/UserStatus.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Exchange Server/Set-VirtualDirectoryUrl/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Exchange Server/Set-VirtualDirectoryUrl/README.md: -------------------------------------------------------------------------------- 1 | # Set-VirtualDirectoryUrl 2 | 3 | ## SYNOPSIS 4 | 5 | Configure Exchange Server 2013 Virtual Directory Url Settings 6 | 7 | Thomas Stensitzki 8 | 9 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 10 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 11 | 12 | Version 2.1, 2021-11-23 13 | 14 | Use GitHub for Please send ideas, comments and suggestions. 15 | 16 | ## LINK 17 | 18 | [https://scripts.granikos.eu](https://scripts.granikos.eu) 19 | 20 | ## DESCRIPTION 21 | 22 | Exchange Server virtual directories (vDirs) require a proper configuration of 23 | internal and external Urls. This is even more important in a co-existence 24 | scenario with legacy Exchange Server versions. 25 | 26 | Read more about Exchange Server 2013+ vDirs [here](https://techcommunity.microsoft.com/t5/exchange-team-blog/configuring-multiple-owa-ecp-virtual-directories-on-the-exchange/ba-p/611217?WT.mc_id=M365-MVP-5003086). 27 | 28 | 29 | ## NOTES 30 | 31 | Requirements 32 | - Windows Server 2016+ 33 | - Exchange Server 2016+ 34 | 35 | Revision History 36 | -------------------------------------------------------------------------------- 37 | 1.0 Initial community release 38 | 2.0 Updated for Exchange Server 2016, 2019, vNEXT 39 | 2.1 PowerShell Hygiene 40 | 41 | ## PARAMETERS 42 | 43 | ### PARAMETER InternalUrl 44 | 45 | The internal url FQDN with leading protocol definition, ie. https://mobile.mcsmemail.de 46 | 47 | ### PARAMETER ExternalUrl 48 | 49 | The internal url FQDN with leading protocol definition, ie. https://mobile.mcsmemail.de 50 | 51 | ## EXAMPLE 52 | 53 | Configure internal and external url for different host headers 54 | .\Set-VirtualDirectoryUrl -InternalUrl https://internal.mcsmemail.de -ExternalUrl https://mobile.mcsmemail.de 55 | 56 | # Stay connected 57 | 58 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 59 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 60 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 61 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 62 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 63 | 64 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 65 | 66 | * Website: [https://granikos.eu](https://granikos.eu) 67 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 68 | -------------------------------------------------------------------------------- /Exchange Server/Set-VirtualDirectoryUrl/Set-VirtualDirectoryUrl.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Configure Exchange Server 2016+ Virtual Directory Url Settings 4 | 5 | Thomas Stensitzki 6 | 7 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 8 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 9 | 10 | Version 2.1, 2021-11-23 11 | 12 | Please send ideas, comments and suggestions to support@granikos.eu 13 | 14 | .LINK 15 | https://scripts.granikos.eu 16 | 17 | .DESCRIPTION 18 | Exchange Server virtual directories (vDirs) require a proper configuration of 19 | internal and external Urls. This is even more important in a co-existence 20 | scenario with legacy Exchange Server versions. 21 | 22 | .NOTES 23 | Requirements 24 | - Windows Server 2016+ 25 | - Exchange Server 2016+ 26 | 27 | Revision History 28 | -------------------------------------------------------------------------------- 29 | 1.0 Initial community release 30 | 2.0 Updated for Exchange Server 2016, 2019, vNEXT 31 | 2.1 PowerShell Hygiene 32 | 33 | .PARAMETER InternalUrl 34 | The internal url FQDN with protocol definition, ie. https://mobile.mcsmemail.de 35 | 36 | .PARAMETER ExternalUrl 37 | The internal url FQDN with protocol definition, ie. https://mobile.mcsmemail.de 38 | 39 | .EXAMPLE 40 | Configure internal and external url for different host headers 41 | .\Set-VirtualDirectoryUrl.ps1 -InternalUrl https://internal.mcsmemail.de -ExternalUrl https://mobile.mcsmemail.de 42 | 43 | #> 44 | 45 | Param( 46 | [string]$InternalUrl='', 47 | [string]$ExternalUrl='', 48 | [string]$AutodiscoverUrl='', 49 | [string]$ServerName = '' 50 | ) 51 | 52 | if($ServerName -ne '') { 53 | # Fetch Exchange Server 54 | $exchangeServers = Get-ExchangeServer -Identity $ServerName 55 | } 56 | else { 57 | # Fetch Exchange Server 2016+ Servers 58 | $exchangeServers = Get-ExchangeServer | Where-Object {($_.IsE15OrLater -eq $true) -and ($_.ServerRole -ilike '*Mailbox*')} 59 | } 60 | 61 | if($InternalUrl -ne '' -and $null -ne $exchangeServers) { 62 | 63 | # Trim trailing "/" 64 | if($InternalUrl.EndsWith('/')) { 65 | $InternalUrl = $InternalUrl.TrimEnd('/') 66 | } 67 | 68 | Write-Output 'The script configures the following servers:' 69 | $exchangeServers | Format-Table -Property Name, AdminDisplayVersion -AutoSize 70 | 71 | Write-Output 'Changing InternalUrl settings' 72 | Write-Output ('New INTERNAL Url: {0}' -f $InternalUrl) 73 | 74 | # Set Internal Urls 75 | $exchangeServers | ForEach-Object{ Set-ClientAccessService -Identity $_.Name -AutodiscoverServiceInternalUri ('{0}/autodiscover/autodiscover.xml' -f $AutodiscoverUrl) -Confirm:$false} 76 | $exchangeServers | Get-WebServicesVirtualDirectory | Set-WebServicesVirtualDirectory -InternalUrl ('{0}/ews/exchange.asmx' -f $InternalUrl) -Confirm:$false 77 | $exchangeServers | Get-OabVirtualDirectory | Set-OabVirtualDirectory -InternalUrl ('{0}/oab' -f $InternalUrl) -Confirm:$false 78 | $exchangeServers | Get-OwaVirtualDirectory | Set-OwaVirtualDirectory -InternalUrl ('{0}/owa' -f $InternalUrl) -Confirm:$false 79 | $exchangeServers | Get-EcpVirtualDirectory | Set-EcpVirtualDirectory -InternalUrl ('{0}/ecp' -f $InternalUrl) -Confirm:$false 80 | $exchangeServers | Get-MapiVirtualDirectory | Set-MapiVirtualDirectory -InternalUrl ('{0}/mapi' -f $InternalUrl) -Confirm:$false 81 | $exchangeServers | Get-ActiveSyncVirtualDirectory | Set-ActiveSyncVirtualDirectory -InternalUrl ('{0}/Microsoft-Server-ActiveSync' -f $InternalUrl) -Confirm:$false 82 | 83 | Write-Output 'InternalUrl changed' 84 | } 85 | 86 | if($ExternalUrl -ne '' -and $null -ne $exchangeServers) { 87 | 88 | # Trim trailing "/" 89 | if($ExternalUrl.EndsWith('/')) { 90 | $ExternalUrl = $ExternalUrl.TrimEnd('/') 91 | } 92 | 93 | Write-Output 'Changing ExternalUrl settings' 94 | Write-Output ('New EXTERNAL Url: {0}' -f $ExternalUrl) 95 | 96 | # Set External Urls 97 | $exchangeServers | Get-WebServicesVirtualDirectory | Set-WebServicesVirtualDirectory -ExternalUrl ('{0}/ews/exchange.asmx' -f $ExternalUrl) -Confirm:$false 98 | $exchangeServers | Get-OabVirtualDirectory | Set-OabVirtualDirectory -ExternalUrl ('{0}/oab' -f $ExternalUrl) -Confirm:$false 99 | $exchangeServers | Get-OwaVirtualDirectory | Set-OwaVirtualDirectory -ExternalUrl ('{0}/owa' -f $ExternalUrl) -Confirm:$false 100 | $exchangeServers | Get-EcpVirtualDirectory | Set-EcpVirtualDirectory -ExternalUrl ('{0}/ecp' -f $ExternalUrl) -Confirm:$false 101 | $exchangeServers | Get-MapiVirtualDirectory | Set-MapiVirtualDirectory -ExternalUrl ('{0}/ecp' -f $ExternalUrl) -Confirm:$false 102 | $exchangeServers | Get-ActiveSyncVirtualDirectory | Set-ActiveSyncVirtualDirectory -ExternalUrl ('{0}/Microsoft-Server-ActiveSync' -f $ExternalUrl) -Confirm:$false 103 | } 104 | 105 | if(($InternalUrl -ne '') -or ($ExternalUrl -ne '')) { 106 | # Query Settings 107 | Write-Output '' 108 | Write-Output 'Current Url settings for CAS AutodiscoverServiceInternalUri' 109 | $exchangeServers | Get-ClientAccessServer | Format-List -Property Identity, AutodiscoverServiceInternalUri 110 | Write-Output 'Current Url settings for Web Services Virtual Directory' 111 | $exchangeServers | Get-WebServicesVirtualDirectory | Format-List -Property Identity, InternalUrl, ExternalUrl 112 | Write-Output 'Current Url settings for OAB Virtual Directory' 113 | $exchangeServers | Get-OabVirtualDirectory | Format-List -Property Identity, InternalUrl, ExternalUrl 114 | Write-Output 'Current Url settings for OWA Virtual Directory' 115 | $exchangeServers | Get-OwaVirtualDirectory | Format-List -Property Identity, InternalUrl,ExternalUrl 116 | Write-Output 'Current Url settings for ECP Virtual Directory' 117 | $exchangeServers | Get-EcpVirtualDirectory | Format-List -Property Identity, InternalUrl,ExternalUrl 118 | Write-Output 'Current Url settings for MAPI Virtual Directory' 119 | $exchangeServers | Get-MapiVirtualDirectory | Format-List -Property Identity, InternalUrl,ExternalUrl 120 | Write-Output 'Current Url settings for ActiveSync Virtual Directory' 121 | $exchangeServers | Get-ActiveSyncVirtualDirectory | Format-List -Property Identity, internalurl, ExternalUrl 122 | } 123 | else { 124 | Write-Output 'Nothing changed!' 125 | } 126 | -------------------------------------------------------------------------------- /Exchange Server/Start-MailboxImport/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 26 Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Exchange Server/Start-MailboxImport/README.md: -------------------------------------------------------------------------------- 1 | # Start-MailboxImport.ps1 2 | 3 | Import one or more PST files into an exisiting mailbox or a archive. 4 | 5 | ## Description 6 | 7 | This script imports one or more PST files into a user mailbox or a user archive as batch. 8 | 9 | PST file names can used as target folder names for import. PST files are renamed to support file name limitations by New-MailboxImportRequest cmdlet. 10 | 11 | All files of a given folder will be imported into the user's mailbox. 12 | 13 | ## Requirements 14 | 15 | - Windows Server 2016+ 16 | - Exchange Server 2016+ 17 | - GlobalFunctions PowerShell Module, [https://www.powershellgallery.com/packages/GlobalFunctions](https://www.powershellgallery.com/packages/GlobalFunctions) 18 | 19 | ## Parameters 20 | 21 | ### Identity 22 | 23 | Mailbox identity in which the PST files get imported 24 | 25 | ### Archive 26 | 27 | Import PST files into the online archive. 28 | 29 | ### FilePath 30 | 31 | Folder which contains the PST files. Has to be an UNC path. 32 | 33 | ### FilenameAsTargetFolder 34 | 35 | Import the PST files into dedicated target folders. The folder name will equal the file name. 36 | 37 | ### BadItemLimit 38 | 39 | Standard is set to 0. Don't max it out because the script doesn't add "AcceptLargeDatalost". 40 | 41 | ### ContinueOnError 42 | 43 | If set the script continue with the next PST file if a import request failed. 44 | 45 | ### SecondsToWait 46 | 47 | Timespan to wait between import request staus checks in seconds. Default: 48 | 49 | ### IncludeFolders 50 | 51 | If set the import would only import the given folder + subfolders. Note: If you want to import subfolders you have to use /* at the end of the folder. (Test/*). 52 | 53 | ### TargetFolder 54 | 55 | Import the files in to definied target folder. Can't be used together with FilenameAsTargetFolder 56 | 57 | ### Recurse 58 | 59 | If this parameter is set all PST files in subfolders will be also imported 60 | 61 | ### RenameFileAfterImport 62 | 63 | Rename successfully imported PST files to simplify a re-run of the script. A .PST file will be renamed to .imported 64 | 65 | ## Examples 66 | 67 | ``` PowerShell 68 | .\Start-MailboxImport.ps1 -Identity testuser -Filepath "\\testserver\share" 69 | ``` 70 | 71 | Import all PST files into the mailbox "testuser" 72 | 73 | ``` PowerShell 74 | .\Start-MailboxImport.ps1 -Identity testuser -Filepath "\\testserver\share\*" -FilenameAsTargetFolder -SecondsToWait 90 75 | ``` 76 | 77 | Import all PST files into the mailbox "testuser". Use PST file name as target folder name. Wait 90 seconds between each status check 78 | 79 | ## Note 80 | 81 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 82 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 83 | 84 | ## Credits 85 | 86 | Written by: Thomas Stensitzki 87 | 88 | Related blog post: [https://granikos.eu/simple-import-of-multiple-pst-files-for-a-single-user/](https://granikos.eu/simple-import-of-multiple-pst-files-for-a-single-user/) 89 | 90 | ### Stay connected 91 | 92 | * Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 93 | * Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 94 | * LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 95 | * YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 96 | * LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 97 | 98 | For more Microsoft 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 99 | 100 | * Website: [https://granikos.eu](https://granikos.eu) 101 | * Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) 102 | -------------------------------------------------------------------------------- /Exchange Server/Test-ExchangeServerHealth/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Exchange Server/Test-ExchangeServerHealth/README.md: -------------------------------------------------------------------------------- 1 | # Test-ExchangeServerHealth.ps1 2 | 3 | 4 | 5 | ## Description 6 | 7 | 8 | ## Requirements 9 | 10 | - Windows Server 2016+ 11 | - Exchange Server 2016+ 12 | 13 | ## Parameters 14 | 15 | ## Examples 16 | 17 | ### Example 1 18 | 19 | Checks all servers in the organization and outputs the results to the shell window. 20 | 21 | ``` PowerShell 22 | .\Test-ExchangeServerHealth.ps1 -Server HO-EX2010-MB1 23 | ``` 24 | 25 | ### Example 2 26 | 27 | Checks the server HO-EX2010-MB1 and outputs the results to the shell window. 28 | 29 | ``` PowerShell 30 | .\Test-ExchangeServerHealth.ps1 -Server HO-EX2010-MB1 31 | ``` 32 | 33 | ### Example 3 34 | 35 | Checks all servers in the organization, outputs the results to the shell window, a HTML report, and emails the HTML report to the address configured in the script. 36 | 37 | ``` PowerShell 38 | .\Test-ExchangeServerHealth.ps1 -ReportMode -SendEmail 39 | ``` 40 | 41 | ## Note 42 | 43 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 44 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 45 | 46 | ## Credits 47 | 48 | Original script by Paul Cunningham: [https://github.com/cunninghamp/Test-ExchangeServerHealth.ps1/tree/master](https://github.com/cunninghamp/Test-ExchangeServerHealth.ps1/tree/master) 49 | 50 | Updated for Exchange Server 2019 by: Thomas Stensitzki 51 | 52 | Related blog post: 53 | 54 | ### Stay connected 55 | 56 | - My Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 57 | - Bluesky: [https://bsky.app/profile/stensitzki.bsky.social](https://bsky.app/profile/stensitzki.bsky.social) 58 | - LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 59 | - YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 60 | - LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 61 | 62 | For more Office 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 63 | 64 | - Website: [https://granikos.eu](https://www.granikos) 65 | - Bluesky: [https://bsky.app/profile/granikos.bsky.social](https://bsky.app/profile/granikos.bsky.social) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Misc/Get-Diskspace/Get-Diskspace.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Fetches disk/volume information from a given computer 4 | 5 | Thomas Stensitzki 6 | 7 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 8 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 9 | 10 | Version 1.4, 2021-11-12 11 | 12 | Please use GitHub repository for ideas, comments, and suggestions. 13 | 14 | .LINK 15 | https://scripts.granikos.eu 16 | 17 | .DESCRIPTION 18 | This script fetches disk/volume information from a given computer and displays 19 | 20 | * Volume name 21 | * Capacity 22 | * Free Space 23 | * Boot Volume Status 24 | * System Volume Status 25 | * File Systemtype 26 | 27 | With -SendMail switch no data is returned to the console. 28 | 29 | .NOTES 30 | Requirements 31 | - Windows Server 2012 R2+ 32 | - Remote WMI access 33 | - Exchange Server Management Shell (for AllExchangeServer switch) 34 | 35 | Revision History 36 | -------------------------------------------------------------------------------- 37 | 1.0 Initial community release 38 | 1.1 Email reports added 39 | 1.11 Send email issue fixed 40 | 1.12 PowerShell hygiene applied 41 | 1.1.3 Version number adjusted, minor PowerShell adjustments 42 | 1.3 Tested for Windows Server 2019, minor PowerShell adjustments 43 | 1.4 TLS 1.2 added 44 | 45 | .PARAMETER ComputerName 46 | Can of the computer to fetch disk information from 47 | 48 | .PARAMETER Unit 49 | Target unit for disk space value (default = GB) 50 | 51 | .PARAMETER AllExchangeServer 52 | Switch to fetch disk space data from all Exchange Servers 53 | 54 | .PARAMETER SendMail 55 | Switch to send an Html report 56 | 57 | .PARAMETER MailFrom 58 | Email address of report sender 59 | 60 | .PARAMETER MailTo 61 | Email address of report recipient 62 | 63 | .PARAMETER MailServer 64 | SMTP Server for email report 65 | 66 | .EXAMPLE 67 | Get disk information from computer MYSERVER 68 | 69 | Get-Diskpace.ps1 -ComputerName MYSERVER 70 | 71 | .EXAMPLE 72 | Get disk information from computer MYSERVER in MB 73 | 74 | Get-Diskpace.ps1 -ComputerName MYSERVER -Unit MB 75 | 76 | .EXAMPLE 77 | Get disk information from all Exchange servers and send html email 78 | 79 | Get-Diskpace.ps1 -AllExchangeServer -SendMail -MailFrom postmaster@sedna-inc.com -MailTo exchangeadmin@sedna-inc.com -MailServer mail.sedna-inc.com 80 | 81 | #> 82 | 83 | [CmdletBinding()] 84 | param( 85 | [string]$ComputerName = $env:COMPUTERNAME, 86 | [string]$Unit = 'GB', 87 | [switch]$AllExchangeServer, 88 | [switch]$SendMail, 89 | [string]$MailFrom = '', 90 | [string]$MailTo = '', 91 | [string]$MailServer = '' 92 | ) 93 | 94 | 95 | $Unit = $Unit.ToUpper() 96 | $now = Get-Date -Format F 97 | $ReportTitle = ('Diskspace Report - {0}' -f ($now)) 98 | $global:Html = '' 99 | 100 | # Set TLS protocol to TLS 1.2 101 | [System.Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 102 | 103 | switch($Unit){ 104 | 'GB' { 105 | $ConvertTo = 1GB 106 | } 107 | 'MB' { 108 | $ConvertTo = 1MB 109 | } 110 | } 111 | 112 | function Get-DiskspaceFromComputer { 113 | [CmdletBinding()] 114 | param( 115 | [string] $ServerName 116 | ) 117 | 118 | if(($Unit -eq 'GB') -or ($Unit -eq 'MB')) { 119 | 120 | $ServerName = $ServerName.ToUpper() 121 | 122 | Write-Output ('Fetching Volume Data from {0}' -f ($ServerName)) 123 | 124 | $WmiResult = Get-WmiObject Win32_Volume -ComputerName $ServerName | Select-Object Name, @{Label="Capacity ($Unit)";Expression={[decimal]::round($_.Capacity/$ConvertTo)}}, @{Label="FreeSpace ($Unit)";Expression={[decimal]::round($_.FreeSpace/$ConvertTo)}}, BootVolume, SystemVolume, FileSystem | Sort-Object Name 125 | $global:Html += $WmiResult | ConvertTo-Html -Fragment -PreContent ('

Server {0}

' -f ($ServerName)) 126 | } 127 | 128 | $WmiResult 129 | } 130 | 131 | Function Test-SendMail { 132 | if( ($SendMail) -and ($MailFrom -ne '') -and ($MailTo -ne '') -and ($MailServer -ne '') ) { 133 | return $true 134 | } 135 | else { 136 | return $false 137 | } 138 | } 139 | 140 | #### MAIN 141 | If (($SendMail) -and (!(Test-SendMail))) { 142 | Throw 'If -SendMail specified, -MailFrom, -MailTo and -MailServer must be specified as well!' 143 | } 144 | 145 | # Some CSS to get a pretty report 146 | $head = @" 147 | 148 | 149 | $($ReportTitle) 150 | 187 | "@ 188 | 189 | if($AllExchangeServer) { 190 | $servers = Get-ExchangeServer | Sort-Object Name 191 | 192 | foreach($server in $servers) { 193 | 194 | $output = Get-DiskspaceFromComputer -ServerName $server.Name 195 | 196 | if(!($SendMail)) { $output | Format-Table -AutoSize } 197 | 198 | } 199 | } 200 | else { 201 | 202 | $output = Get-DiskspaceFromComputer -ServerName $ComputerName 203 | 204 | if(!($SendMail)) { $output | Format-Table -AutoSize } 205 | 206 | } 207 | 208 | if($SendMail) { 209 | 210 | [string]$Body = ConvertTo-Html -Body $global:Html -Title 'Status' -Head $head 211 | 212 | Send-MailMessage -From $MailFrom -To $Mailto -SmtpServer $MailServer -Body $Body -BodyAsHtml -Subject $ReportTitle 213 | 214 | Write-Output ('Email sent to {0}' -f ($MailTo)) 215 | } -------------------------------------------------------------------------------- /Misc/Get-Diskspace/Get-Diskspace.ps1.txt: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | Fetches disk/volume information from a given computer 5 | 6 | Thomas Stensitzki 7 | 8 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 9 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 10 | 11 | Version 1.3, 2019-10-16 12 | 13 | Please send ideas, comments and suggestions to support@granikos.eu 14 | 15 | .LINK 16 | 17 | https://scripts.granikos.eu 18 | 19 | .DESCRIPTION 20 | 21 | This script fetches disk/volume information from a given computer and displays 22 | 23 | * Volume name 24 | * Capacity 25 | * Free Space 26 | * Free Space in percent 27 | * Boot Volume Status 28 | * System Volume Status 29 | * File Systemtype 30 | 31 | With -SendMail switch no data is returned to the console. 32 | 33 | .NOTES 34 | 35 | Requirements 36 | - Windows Server 2012 R2+ 37 | - Remote WMI 38 | - Exchange Server Management Shell 2013+ 39 | 40 | Revision History 41 | -------------------------------------------------------------------------------- 42 | 1.0 Initial community release 43 | 1.1 Email reports added 44 | 1.11 Send email issue fixed 45 | 1.2 Some PowerShell hygiene, Html CSS changes 46 | 1.3 Tested for Windows Server 2019 47 | 1.4 Free Space percentage added, HTML formatting changed 48 | 49 | .PARAMETER ComputerName 50 | Can of the computer to fetch disk information from 51 | 52 | .PARAMETER Unit 53 | Target unit for disk space value (default = GB) 54 | 55 | .PARAMETER AllExchangeServer 56 | Switch to fetch disk space data from all Exchange Servers 57 | 58 | .PARAMETER SendMail 59 | Switch to send an Html report 60 | 61 | .PARAMETER MailFrom 62 | Email address of report sender 63 | 64 | .PARAMETER MailTo 65 | Email address of report recipient 66 | 67 | .PARAMETER MailServer 68 | SMTP Server for email report 69 | 70 | .EXAMPLE 71 | Get disk information from computer MYSERVER 72 | 73 | .\Get-Diskpace.ps1 -ComputerName MYSERVER 74 | 75 | .EXAMPLE 76 | Get disk information from computer MYSERVER in MB 77 | 78 | .\Get-Diskpace.ps1 -ComputerName MYSERVER -Unit MB 79 | 80 | .EXAMPLE 81 | Get disk information from all Exchange servers and send html email 82 | 83 | .\Get-Diskpace.ps1 -AllExchangeServer -SendMail -MailFrom postmaster@sedna-inc.com -MailTo exchangeadmin@sedna-inc.com -MailServer mail.sedna-inc.com 84 | 85 | #> 86 | 87 | [CmdletBinding()] 88 | param( 89 | [string] $ComputerName = $env:COMPUTERNAME, 90 | [ValidateSet('MB','GB')] 91 | [string] $Unit = 'GB', 92 | [switch] $AllExchangeServer, 93 | [switch] $SendMail, 94 | [string] $MailFrom = '', 95 | [string] $MailTo = '', 96 | [string] $MailServer = '' 97 | ) 98 | 99 | $scriptVersion = '1.4' 100 | $Unit = $Unit.ToUpper() 101 | $now = Get-Date -Format F 102 | $ReportTitle = ('Diskspace Report - {0}' -f ($now)) 103 | $script:Html = '' 104 | 105 | switch($Unit){ 106 | 'GB' { 107 | $ConvertTo = 1GB 108 | } 109 | 'MB' { 110 | $ConvertTo = 1MB 111 | } 112 | } 113 | 114 | function Get-DiskspaceFromComputer { 115 | [CmdletBinding()] 116 | param( 117 | [string] $ServerName = '' 118 | ) 119 | 120 | if(($Unit -eq 'GB') -or ($Unit -eq 'MB')) { 121 | 122 | $ServerName = $ServerName.ToUpper() 123 | 124 | Write-Output ('Fetching Volume Data from {0}' -f ($ServerName)) 125 | 126 | # This assumes that the account running this script has permissions to access the remote computer using WMI 127 | $wmiRaw = Get-WmiObject -Class Win32_Volume -ComputerName $ServerName | where{$_.name -notlike '\\?\*'} 128 | 129 | $wmi = $wmiRaw | Select-Object -Property Name, ` 130 | @{Label="Capacity ($Unit)";Expression={[decimal]::round($_.Capacity/$ConvertTo)}}, ` 131 | @{Label="FreeSpace ($Unit)";Expression={[decimal]::round($_.FreeSpace/$ConvertTo)}}, ` 132 | @{Label="FreeSpace (%)";Expression={[int](($_.FreeSpace/$_.Capacity) * 100).ToString("#")} }, ` 133 | BootVolume, SystemVolume, FileSystem ` 134 | | Sort-Object -Property Name 135 | 136 | $sumCapacity = ($wmiRaw | Measure-Object Capacity -Sum).Sum / 1gb 137 | 138 | # Add WMI data to HTML 139 | $script:Html += $wmi | ConvertTo-Html -Fragment -PreContent ('

Server {0}

' -f ($ServerName)) -Postcontent ('

{0:0} GB


' -f $sumCapacity) 140 | } 141 | 142 | $wmi 143 | } 144 | 145 | Function Test-SendMail { 146 | if( ($SendMail) -and ($MailFrom -ne '') -and ($MailTo -ne '') -and ($MailServer -ne '') ) { 147 | return $true 148 | } 149 | else { 150 | return $false 151 | } 152 | } 153 | 154 | #### MAIN 155 | If (($SendMail) -and (!(Test-SendMail))) { 156 | Throw 'If -SendMail specified, -MailFrom, -MailTo and -MailServer must be specified as well!' 157 | } 158 | 159 | # Some CSS to get a pretty report 160 | $head = @" 161 | 162 | $($ReportTitle) 163 | 214 | "@ 215 | 216 | $script:Html += '

Note: Mounted VSS snapshots are excluded from this report

' 217 | 218 | if($AllExchangeServer) { 219 | 220 | # Fetch all Exchange Servers except Edge-Transport-Systems 221 | $servers = Get-ExchangeServer | where {$_.ServerRole -ne "Edge"}| Sort-Object -Property Name 222 | 223 | foreach($server in $servers) { 224 | 225 | $output = Get-DiskspaceFromComputer -ServerName $server.Name 226 | 227 | if(!($SendMail)) { $output | Format-Table -AutoSize } 228 | } 229 | } 230 | else { 231 | 232 | $output = Get-DiskspaceFromComputer -ServerName $ComputerName 233 | 234 | if(!($SendMail)) { $output | Format-Table -AutoSize } 235 | } 236 | 237 | if($SendMail) { 238 | [string]$Body = ConvertTo-Html -Body $script:Html -Title 'Status' -Head $head -Postcontent ('
Script version: {0}
' -f $scriptVersion) 239 | 240 | Send-MailMessage -From $MailFrom -To $Mailto -SmtpServer $MailServer -Body $Body -BodyAsHtml -Subject $ReportTitle 241 | 242 | Write-Output ('Email sent to {0}' -f ($MailTo)) 243 | } -------------------------------------------------------------------------------- /Misc/Get-Diskspace/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Misc/Get-Diskspace/README.md: -------------------------------------------------------------------------------- 1 | # Get-Diskspace.ps1 2 | 3 | Fetches disk/volume information from a given computer 4 | 5 | ## Description 6 | 7 | This script fetches disk/volume information from a given computer and displays 8 | 9 | - Volume name 10 | - Capacity 11 | - Free Space 12 | - Boot Volume Status 13 | - System Volume Status 14 | - File System Type 15 | 16 | With -SendMail switch no data is returned to the console. 17 | 18 | ## Requirements 19 | 20 | - Windows Server 2012R2, 2016, 2019 21 | - Exchange Server 2013+ (for AllExchangeServer switch) 22 | - WMI access to remote computers 23 | 24 | ## Parameters 25 | 26 | ### ComputerName 27 | 28 | Can of the computer to fetch disk information from 29 | 30 | ### Unit 31 | 32 | Target unit for disk space value (default = GB) 33 | 34 | ### AllExchangeServer 35 | 36 | Switch to fetch disk space data from all Exchange Servers 37 | 38 | ### SendMail 39 | 40 | Switch to send an Html report 41 | 42 | ### MailFrom 43 | 44 | Email address of report sender 45 | 46 | ### MailTo 47 | 48 | Email address of report recipient 49 | 50 | ### MailServer 51 | 52 | SMTP Server for email report 53 | 54 | ## Examples 55 | 56 | ``` PowerShell 57 | .\Get-Diskpace.ps1 -ComputerName MYSERVER 58 | ``` 59 | 60 | Get disk information from computer MYSERVER 61 | 62 | ``` PowerShell 63 | .\Get-Diskpace.ps1 -ComputerName MYSERVER -Unit MB 64 | ``` 65 | 66 | Get disk information from computer MYSERVER in MB 67 | 68 | ``` PowerShell 69 | .\Get-Diskpace.ps1 -AllExchangeServer -SendMail -MailFrom postmaster@sedna-inc.com -MailTo exchangeadmin@sedna-inc.com -MailServer mail.sedna-inc.com 70 | ``` 71 | 72 | Get disk information from all Exchange servers and send html email 73 | 74 | ## Note 75 | 76 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 77 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 78 | 79 | ## Credits 80 | 81 | Written by: Thomas Stensitzki 82 | 83 | ### Stay connected 84 | 85 | - My Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 86 | - Bluesky: [https://bsky.app/profile/stensitzki.bsky.social](https://bsky.app/profile/stensitzki.bsky.social) 87 | - LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 88 | - YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 89 | - LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 90 | 91 | For more Office 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 92 | 93 | - Website: [https://granikos.eu](https://www.granikos) 94 | - Bluesky: [https://bsky.app/profile/granikos.bsky.social](https://bsky.app/profile/granikos.bsky.social) -------------------------------------------------------------------------------- /Misc/Get-Diskspace/Screenshot-Email.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apoc70/PowerShell-Scripts/ef43113b389502d309e246891fa9c7a3ad5a147e/Misc/Get-Diskspace/Screenshot-Email.PNG -------------------------------------------------------------------------------- /Misc/Get-Diskspace/Screenshot-Shell.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apoc70/PowerShell-Scripts/ef43113b389502d309e246891fa9c7a3ad5a147e/Misc/Get-Diskspace/Screenshot-Shell.PNG -------------------------------------------------------------------------------- /Misc/Get-SoftwareInventory/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016,2025 Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Misc/Get-SoftwareInventory/README.md: -------------------------------------------------------------------------------- 1 | # Get-SoftwareInventory.ps1 2 | 3 | Script to generate an html reports of installed software, installed updates and installed components on a remote computer 4 | 5 | ## Description 6 | 7 | This script utilizes the slightly modified function Get-RemoteProgram by Jaap Brasser to fetch information about installed software. 8 | 9 | ## Parameters 10 | 11 | ### RemoteComputer 12 | 13 | Name of the computer to fetch data from (default: local computer) 14 | 15 | ### SoftwareFilter 16 | 17 | Filter for installed software. Only software matching the filter will be included in the report. 18 | 19 | ### ProgramsOnly 20 | 21 | Switch to only fetch installed programs and exclude updates and components 22 | 23 | ### SendMail 24 | 25 | Switch to send an Html report 26 | 27 | ### MailFrom 28 | 29 | Email address of report sender 30 | 31 | ### MailTo 32 | 33 | Email address of report recipient 34 | 35 | ### MailServer 36 | 37 | SMTP Server for email report 38 | 39 | ## Examples 40 | 41 | ``` PowerShell 42 | .\Get-SoftwareInventory.ps1 -SendMail -MailFrom postmaster@mcsmemail.de -MailTo it-support@mcsmemail.de -MailServer mymailserver.mcsmemail.de -RemoteComputer SERVER01,SERVER02 43 | ``` 44 | 45 | Get software information about SERVER01, SERVER02 and send email report 46 | 47 | ``` PowerShell 48 | .\Get-SoftwareInventory.ps1 -RemoteComputer SERVER01,SERVER02 49 | ``` 50 | 51 | Get software information about SERVER01, SERVER02 and write report to disk only 52 | 53 | ## Note 54 | 55 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 56 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 57 | 58 | ## Credits 59 | 60 | Written by: Thomas Stensitzki 61 | 62 | ### Stay connected 63 | 64 | - My Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 65 | - Bluesky: [https://bsky.app/profile/stensitzki.bsky.social](https://bsky.app/profile/stensitzki.bsky.social) 66 | - LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 67 | - YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 68 | - LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 69 | 70 | For more Office 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 71 | 72 | - Website: [https://granikos.eu](https://www.granikos) 73 | - Bluesky: [https://bsky.app/profile/granikos.bsky.social](https://bsky.app/profile/granikos.bsky.social) 74 | 75 | ### Additional Credits 76 | 77 | - Jaap Brasser 78 | - Georges Zwingelstein 79 | -------------------------------------------------------------------------------- /Misc/Get-SoftwareInventory/Run-GetSoftwareInventory.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Predefined script to fetch software inventory and send email report 3 | 4 | Not a real script, but Copyright (c) 2025 Thomas Stensitzki 5 | #> 6 | 7 | # Define server list 8 | $server = @('SERVER01','server02') 9 | 10 | .\Get-SoftwareInventory.ps1 -SendMail -MailFrom postmaster@mcsmemail.de -MailTo it@mcsmemail.de -MailServer myserver@mcsmemail.de -RemoteComputer $server -------------------------------------------------------------------------------- /Misc/Get-SoftwareInventory/Run-GetSoftwareInventoryExchange.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Script to fetch software inventory across all Exchange servers and some additional servers amd send the result via email. 3 | Must be executed in Exchange Management Shell (EMS) 4 | 5 | Not fancy script, but Copyright (c) 2025 Thomas Stensitzki 6 | #> 7 | 8 | # Define initial server list 9 | $server = @('SERVER01','server02') 10 | 11 | # Fetch Exchange server list and add to server list 12 | # Requires Exchange Management Shell 13 | Get-ExchangeServer | Select-Object Name | ForEach-Object{$server += $_.Name} | Sort-Object 14 | 15 | # Fetch software inventory for all servers 16 | # Requires Get-SoftwareInventory.ps1 in the same folder as this script 17 | .\Get-SoftwareInventory.ps1 -SendMail -MailFrom postmaster@mcsmail.de -MailTo it@mcsmail.de -MailServer mobile.mcsmail.de -RemoteComputer $server -------------------------------------------------------------------------------- /Misc/New-TestUser/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Thomas Stensitzki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Misc/New-TestUser/New-TestUser.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [int]$UserCount = 5, 3 | [string]$Company = 'Varunagroup', 4 | [string]$UserNameCsv = '', 5 | [switch]$RandomPassword, 6 | [string]$TestUserPrefix = 'TestUser', 7 | [string]$PreferredLanguage = 'de-DE', 8 | [string]$TargetOU = 'OU=IT,dc=varunagroup,dc=de', 9 | [string]$TestUserOU = 'Test User', 10 | [string]$UpnDomain = 'varunagroup.de' 11 | ) 12 | 13 | $DefaultPassword = 'Pa55w.rd' 14 | 15 | # User name prefix 16 | # New user object will be named TestUser1, TestUser2, ... 17 | 18 | # User object properties 19 | $GivenName = 'Test' 20 | $Surname = 'User' 21 | $JobTitle = @('Junior Consultant','Senior Consultant','Technical Consultant','Business Consultant','Sales Professional','Team Lead') 22 | 23 | # Import Active Directory PowerShell Module 24 | Import-Module -Name ActiveDirectory 25 | 26 | # Build OU Path 27 | $TestUserOUPath = ('OU={0},{1}' -f $TestUserOU, $TargetOU) 28 | 29 | # Check if OU exists 30 | $OUExists = $false 31 | 32 | try { 33 | $OUExists = [adsi]::Exists("LDAP://$TestUserOUPath") 34 | } 35 | catch { 36 | $OUExists =$true 37 | } 38 | 39 | if(-not $OUExists) { 40 | # Create new organizational unit for test users 41 | New-ADOrganizationalUnit -Name $TestUserOU -Path $TargetOU -ProtectedFromAccidentalDeletion:$false -Confirm:$false 42 | } 43 | else { 44 | Write-Warning -Message ('OU {0} exists please delete the OU and user objects manually, before running this script.' -f $TestUserOUPath) 45 | Exit 46 | } 47 | 48 | Write-Output -InputObject ('Creating {0} user object in {1}' -f $UserCount, $TestUserOUPath) 49 | 50 | # Create new user objects 51 | 1..$UserCount | ForEach-Object { 52 | 53 | # Get a random number for selecting a job title 54 | $random = Get-Random -Minimum 0 -Maximum (($JobTitle | Measure-Object). Count - 1) 55 | 56 | # Set user password 57 | if($RandomPassword) { 58 | # Create a random password 59 | $UserPassword = ConvertTo-SecureString -String (-join ((33..93) + (97..125) | Get-Random -Count 25 | % {[char]$_})) -AsPlainText -Force 60 | } 61 | else { 62 | # Use a fixed password 63 | $UserPassword = ConvertTo-SecureString -String $DefaultPassword -AsPlainText -Force 64 | } 65 | 66 | # Create a new user object 67 | # Adjust user name template and other attributes as needed 68 | New-ADUser -Name ('{0}{1}' -f $TestUserPrefix, $_) ` 69 | -DisplayName ('{0} {1}' -f $TestUserPrefix, $_) ` 70 | -GivenName $GivenName ` 71 | -Surname (('{0}{1}' -f $Surname, $_)) ` 72 | -OtherAttributes @{title=$JobTitle[$random];company=$Company;preferredLanguage=$PreferredLanguage} ` 73 | -Path $TestUserOUPath ` 74 | -AccountPassword $UserPassword ` 75 | -UserPrincipalName ('{0}.{1]@{2}' -f $GivenName, (('{0}{1}' -f $Surname, $_)), $UpnDomain) ` 76 | -Enabled:$True ` 77 | -Confirm:$false 78 | } -------------------------------------------------------------------------------- /Misc/New-TestUser/README.md: -------------------------------------------------------------------------------- 1 | # New-TestUser.ps1 2 | Short script to create Active Directory test user objects. 3 | 4 | ## Description 5 | 6 | The script creates a new organizational unit (OU) and a configured number of user accounts. 7 | 8 | You must configure all variables in the script to suit your environment. 9 | 10 | ## Examples 11 | 12 | ``` PowerShell 13 | .\New-TestUser.ps1 14 | ``` 15 | 16 | ## Note 17 | 18 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE 19 | RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 20 | 21 | ## Credits 22 | 23 | Written by: Thomas Stensitzki 24 | 25 | ### Stay connected 26 | 27 | - My Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 28 | - Bluesky: [https://bsky.app/profile/stensitzki.bsky.social](https://bsky.app/profile/stensitzki.bsky.social) 29 | - LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 30 | - YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 31 | - LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 32 | 33 | For more Office 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 34 | 35 | - Website: [https://granikos.eu](https://www.granikos) 36 | - Bluesky: [https://bsky.app/profile/granikos.bsky.social](https://bsky.app/profile/granikos.bsky.social) -------------------------------------------------------------------------------- /Misc/New-TestUser/TestUserNames.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apoc70/PowerShell-Scripts/ef43113b389502d309e246891fa9c7a3ad5a147e/Misc/New-TestUser/TestUserNames.xlsx -------------------------------------------------------------------------------- /Misc/Set-PageFileSize/Set-PageFileSize.ps1: -------------------------------------------------------------------------------- 1 | # Ermitteln der aktuellen Größe des Arbeitsspeichers 2 | $TotalMemory = (Get-WmiObject -Class Win32_ComputerSystem).TotalPhysicalMemory 3 | $TotalMemoryMB = [math]::Round($TotalMemory / 1MB) 4 | 5 | # Berechnen der Größe der Auslagerungsdatei (25% des Arbeitsspeichers) 6 | $PageFileSizeMB = [math]::Round($TotalMemoryMB * 0.25) 7 | 8 | # Ausgabe der ermittelten Werte 9 | Write-Output "Gesamter Arbeitsspeicher: $TotalMemoryMB MB" 10 | Write-Output "Größe der Auslagerungsdatei (25% des Arbeitsspeichers): $PageFileSizeMB MB" 11 | 12 | # Deaktivieren der automatischen Verwaltung der Auslagerungsdatei 13 | $computersys = Get-WmiObject Win32_ComputerSystem -EnableAllPrivileges 14 | $computersys.AutomaticManagedPagefile = $False 15 | $computersys.Put() 16 | 17 | # Festlegen der Größe der Auslagerungsdatei auf 25% des Arbeitsspeichers 18 | Set-CimInstance -Query "SELECT * FROM Win32_PageFileSetting" -Property @{ 19 | InitialSize = $PageFileSizeMB 20 | MaximumSize = $PageFileSizeMB 21 | } 22 | 23 | # Bestätigung der neuen Einstellungen 24 | Write-Output "Die Auslagerungsdatei wurde erfolgreich auf $PageFileSizeMB MB konfiguriert." 25 | Write-Output "Bitte starte den Computer neu, um die Änderungen zu übernehmen." 26 | -------------------------------------------------------------------------------- /Network/Test-DNSRecords/Domains.txt: -------------------------------------------------------------------------------- 1 | Domain,TenantDomain 2 | varunagroup.de, 3 | ixion-inc.de, 4 | granikos.eu,granikoseu.onmicrosoft.com -------------------------------------------------------------------------------- /Network/Test-NetworkAdapters/Test-NetworkAdapters.ps1: -------------------------------------------------------------------------------- 1 | # Abrufen aller Netzwerkschnittstellen und ihrer IP-Adressen 2 | $networkAdapters = Get-NetIPAddress | Where-Object { $_.InterfaceAlias -notlike "*Loopback*" } 3 | 4 | foreach ($adapter in $networkAdapters) { 5 | $interfaceAlias = $adapter.InterfaceAlias 6 | $ipAddress = $adapter.IPAddress 7 | $addressFamily = $adapter.AddressFamily 8 | $dhcpEnabled = (Get-NetIPInterface -InterfaceAlias $interfaceAlias -AddressFamily $addressFamily).Dhcp 9 | 10 | # Ausgabe der Netzwerkkonfiguration je nach Adressfamilie 11 | if ($addressFamily -eq "IPv4") { 12 | Write-Output "Netzwerkadapter: $interfaceAlias (IPv4)" 13 | Write-Output "IP-Adresse : $ipAddress" 14 | Write-Output "DHCP aktiviert : $dhcpEnabled" 15 | Write-Output "-----------------------------" 16 | 17 | # Überprüfen, ob DHCP deaktiviert ist (statische IP-Adresse) 18 | if ($dhcpEnabled -eq "Disabled") { 19 | Write-Output "> Der Adapter $interfaceAlias (IPv4) ist für eine fest zugewiesene IP-Adresse konfiguriert." 20 | } else { 21 | Write-Output "> Der Adapter $interfaceAlias (IPv4) verwendet DHCP." 22 | } 23 | Write-Output "=============================" 24 | } elseif ($addressFamily -eq "IPv6") { 25 | Write-Output "Netzwerkadapter: $interfaceAlias (IPv6)" 26 | Write-Output "IP-Adresse : $ipAddress" 27 | Write-Output "DHCP aktiviert : $dhcpEnabled" 28 | Write-Output "-----------------------------" 29 | 30 | # Überprüfen, ob DHCP deaktiviert ist (statische IP-Adresse) 31 | if ($dhcpEnabled -eq "Disabled") { 32 | Write-Output "> Der Adapter $interfaceAlias (IPv6) ist für eine fest zugewiesene IP-Adresse konfiguriert." 33 | } else { 34 | Write-Output "> Der Adapter $interfaceAlias (IPv6) verwendet DHCP." 35 | } 36 | Write-Output "=============================" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /README-IMAGES/Add-CustomCalendarEvents-OWA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apoc70/PowerShell-Scripts/ef43113b389502d309e246891fa9c7a3ad5a147e/README-IMAGES/Add-CustomCalendarEvents-OWA.png -------------------------------------------------------------------------------- /README-IMAGES/Add-CustomCalendarEvents-PowerShell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apoc70/PowerShell-Scripts/ef43113b389502d309e246891fa9c7a3ad5a147e/README-IMAGES/Add-CustomCalendarEvents-PowerShell.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerShell Scripts 2 | 3 | This GitHub Repository contains most of my public PowerShell scripts. In the past I used dedicated repositories per script. I will archive those repositories after moving the script to this repository. C#-related projects remain in separate repositories. 4 | 5 | ## Exchange Online 6 | 7 | Script for Exchange Online 8 | 9 | - [Move-MigrationUser.ps1](/Exchange%20Online/Move-MigrationUser) 10 | 11 | This script creates a new migration batch and moves migration users from one batch to the new batch 12 | 13 | ## Modern Exchange Server 14 | 15 | Scripts for Exchange 2013, 2016, and 2019 16 | 17 | - [Copy-ReceiveConnector.ps1](/Exchange%20Server/Copy-ReceiveConnector) 18 | 19 | Copy a selected receive connector and it's configuration and permissions to other Exchange Servers 20 | 21 | - [Export-MessageQueue.ps1](/Exchange%20Server/Export-MessageQueue) 22 | 23 | Export messages from a transport queue to file system for manual replay 24 | 25 | - [Get-ExchangeEnvironmentReport.ps1](/Exchange%20Server/Get-ExchangeEnvironmentReport) 26 | 27 | Creates an HTML report describing the On-Premises Exchange environment. 28 | 29 | - [Get-RemoteSmtpServers.ps1](/Exchange%20Server/Get-RemoteSmtpServers) 30 | 31 | Fetch all remote SMTP servers from Exchange receive connector logs 32 | 33 | - [Import-EdgeSubscription.ps1](/Exchange%20Server/Import-EdgeSubscription) 34 | 35 | Little helper script when working with Edge Transport Server subscriptions 36 | 37 | - [New-RoomMailbox.ps1](/Exchange%20Server/New-RoomMailbox) 38 | 39 | This scripts creates a new room mailbox and security groups for full access and and send-as delegation. As a third security group a dedicated group for allowed users to book the new room is created. The CalenderBooking security group is only created, but not assigned to the room mailbox. Security groups are created using a naming convention. 40 | 41 | - [New-TeamMailbox.ps1](/Exchange%20Server/New-TeamMailbox) 42 | 43 | Creates a new shared mailbox, security groups for full access and send-as permission and adds the security groups to the shared mailbox configuration. 44 | 45 | - [Purge-LogFiles.ps1](/Exchange%20Server/Purge-LogFiles) 46 | 47 | PowerShell script for modern Exchange Server environments to clean up Exchange Server and IIS log files 48 | 49 | - [Start-MailboxImport.ps1](/Exchange%20Server/Start-MailboxImport) 50 | 51 | Import one or more pst files into an exisiting mailbox or a archive 52 | 53 | 54 | ## Legacy Exchange Server 55 | 56 | Scripts for Exchange Server 2010 and older 57 | 58 | - TBD 59 | 60 | ## Misc 61 | 62 | Some usefull scripts not Exchange related 63 | 64 | - [Get-Diskspace.ps1](/Misc/Get-Diskspace) 65 | 66 | Fetches disk/volume information from a given computer 67 | 68 | ## Network 69 | 70 | Some useful network related scripts 71 | 72 | - [Test-DNSRecords.ps1](/Network/Test-DNSRecords) 73 | 74 | ### Stay connected 75 | 76 | - My Blog: [https://blog.granikos.eu](https://blog.granikos.eu) 77 | - Bluesky: [https://bsky.app/profile/stensitzki.eu](https://bsky.app/profile/stensitzki.eu) 78 | - LinkedIn: [https://www.linkedin.com/in/thomasstensitzki](https://www.linkedin.com/in/thomasstensitzki) 79 | - YouTube: [https://www.youtube.com/@ThomasStensitzki](https://www.youtube.com/@ThomasStensitzki) 80 | - LinkTree: [https://linktr.ee/stensitzki](https://linktr.ee/stensitzki) 81 | 82 | For more Office 365, Cloud Security, and Exchange Server stuff checkout services provided by Granikos 83 | 84 | - Website: [https://granikos.eu/](https://granikos.eu/) 85 | - Bluesky: [https://bsky.app/profile/granikos.eu](https://bsky.app/profile/granikos.eu) -------------------------------------------------------------------------------- /Script Template/PowerShell-Script-Template.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | Short description 5 | 6 | Remove any comment section not used, e.g., LINK, INPUTS, or OUTPUTS 7 | Additonal information on comment based help: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_comment_based_help 8 | 9 | .DESCRIPTION 10 | 11 | Long description 12 | 13 | THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK 14 | OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. 15 | 16 | .NOTES 17 | 18 | Requirements 19 | 20 | - Windows Server 2019+ 21 | 22 | Revision History 23 | -------------------------------------------------------------------------------- 24 | 1.0 Initial release 25 | 1.1 xxxx 26 | 27 | .LINK 28 | 29 | https://scripts.granikos.eu 30 | 31 | .PARAMETER ExportFileWithoutDefaultValue 32 | 33 | Madatory export file name 34 | 35 | .PARAMETER ExportFileWithDefaultValue 36 | 37 | Export file name example using a default value of 'ExportFile.csv'' 38 | 39 | .EXAMPLE 40 | 41 | Get-SomeCmdlet.ps1 -SomeParameter1 'ExportToUTF8.csv' 42 | 43 | Executes the script and exports the gathered information to a CSV file named ExportToUTF8.csv 44 | 45 | #> 46 | 47 | # Parameter section with examples 48 | # Additional information parameters: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters 49 | [CmdletBinding()] 50 | param( 51 | [Parameter( 52 | Mandatory=$true, 53 | HelpMessage = "Full path of the output file to be generated. If only filename is specified, then the output file will be generated in the current directory.")] 54 | [ValidateNotNull()] 55 | [string]$ExportFileWithoutDefaultValue, 56 | [string]$ExportFileWithDefaultValue = 'ExportFile.csv' 57 | ) 58 | 59 | #region Initialize Script 60 | 61 | # Measure script running time 62 | $StopWatch = [System.Diagnostics.Stopwatch]::StartNew() 63 | 64 | $script:ScriptPath = Split-Path $script:MyInvocation.MyCommand.Path 65 | $script:ScriptName = $MyInvocation.MyCommand.Name 66 | 67 | # Load required module for logging 68 | if($null -ne (Get-Module -Name GlobalFunctions -ListAvailable).Version) { 69 | Import-Module -Name GlobalFunctions 70 | } 71 | else { 72 | Write-Warning -Message 'Unable to load GlobalFunctions PowerShell module.' 73 | Write-Warning -Message 'Open an administrative PowerShell session and run Import-Module GlobalFunctions' 74 | Write-Warning -Message 'Please check http://bit.ly/GlobalFunctions for further instructions' 75 | exit 76 | } 77 | 78 | # Create a logging oobject 79 | $logger = New-Logger -ScriptRoot $script:ScriptPath -ScriptName $script:ScriptName -LogFileRetention 14 80 | $logger.Purge() 81 | $logger.Write('Script started') 82 | 83 | #endregion 84 | 85 | #region Functions 86 | 87 | <# 88 | Load script settings from dedicated settings.xml 89 | The following comment block contains an XML example 90 | 91 | 92 | 93 | 94 | 4711 95 | 96 | 97 | 98 | #> 99 | function LoadScriptSettings { 100 | if (Test-Path -Path ('{0}\Settings.xml' -f $script:ScriptPath)) { 101 | # Load Script settings 102 | [xml]$Config = Get-Content -Path ('{0}\Settings.xml' -f $script:ScriptPath) 103 | 104 | Write-Verbose -Message 'Loading script settings' 105 | 106 | # Group settings 107 | $someValue = $Config.Settings.Group.SomeValue 108 | 109 | Write-Verbose -Message 'Script settings loaded' 110 | } 111 | else { 112 | Write-Error -Message 'Script settings file settings.xml missing. Please check documentation.' 113 | exit 99 114 | } 115 | } 116 | 117 | <# 118 | 119 | Function to prompt user for yes/no 120 | 121 | Depending on the choice order, the functions returns 122 | 0 = YES 123 | 1 = NO 124 | 125 | Example: Interactive Y/N query before running additional code 126 | 127 | if((Request-Choice -Caption ('Do you want to apply the settings to {0}?' -f $SomeVariable)) -eq 0) { 128 | # Yes Option 129 | } 130 | else { 131 | # No Option 132 | } 133 | 134 | #> 135 | function Request-Choice { 136 | [CmdletBinding()] 137 | param( 138 | [Parameter( 139 | Mandatory=$true, 140 | HelpMessage = "Provide a caption for the Y/N question.")] 141 | [string]$Caption 142 | ) 143 | $choices = [System.Management.Automation.Host.ChoiceDescription[]]@('&Yes','&No') 144 | [int]$defaultChoice = 1 145 | 146 | $choiceReturn = $Host.UI.PromptForChoice($Caption, '', $choices, $defaultChoice) 147 | 148 | return $choiceReturn 149 | } 150 | 151 | #endregion 152 | 153 | #region MAIN 154 | 155 | # 1. Load script settings 156 | LoadScriptSettings 157 | 158 | <# 159 | The main code 160 | #> 161 | 162 | #endregion 163 | 164 | #region End Script 165 | 166 | # Stop watch 167 | $StopWatch.Stop() 168 | 169 | # Write script runtime 170 | Write-Verbose -Message ('It took {0:00}:{1:00}:{2:00} to run the script.' -f $StopWatch.Elapsed.Hours, $StopWatch.Elapsed.Minutes, $StopWatch.Elapsed.Seconds) 171 | $logger.Write( ('It took {0:00}:{1:00}:{2:00} to run the script.' -f $StopWatch.Elapsed.Hours, $StopWatch.Elapsed.Minutes, $StopWatch.Elapsed.Seconds) ) 172 | $logger.Write('Script finished') 173 | 174 | #endregion --------------------------------------------------------------------------------