├── Active-Directory ├── Add-AD-NewDomaintoMailboxes.ps1 ├── Clear-AD-ManagerForAllUsersInOU.ps1 └── Update-AD-TitleForAllUsersInOU.ps1 ├── Applications ├── Check-Windows-ApplicationIsInstalled.ps1 ├── Create-Windows-Shortcut.ps1 ├── Download-Windows-WordNormalTemplate.ps1 ├── Install-Windows-MicrosoftTeamsX64.ps1 └── Manage-Windows-WordAutocorrectEntries.ps1 ├── Azure └── Export-Azure-ResourceTemplatestoJSON.ps1 ├── BuyMeACoffee.png ├── Help-and-Usage ├── CWAutomateScriptExample.png ├── Naming-Conventions.md ├── Synopsis-Template.ps1 └── Using-Scripts-in-CWAutomate.md ├── LICENSE ├── Local-Accounts ├── Create-Windows-LocalAdminAccount.ps1 ├── Disable-Windows-LocalAccount.ps1 └── List-Windows-LocalAdminAccounts.ps1 ├── Office365-Mail └── Enable-Office365-Mail-PlusAddressing.ps1 ├── Office365-Sharepoint ├── Delete-SharePoint-OldFileVersions.ps1 ├── Replace-SharePoint-Permission.ps1 └── Update-SharePoint-SiteLogos.ps1 ├── Onboarding └── Remove-Windows-UnwantedApps.ps1 ├── Patching └── Get-Windows-MissingUpdates.ps1 ├── README.md ├── SECURITY.md ├── Security-Hardening ├── Disable-Quick-Assist.ps1 ├── Disable-Windows-SMB1.ps1 └── Enable-Quick-Assist.ps1 ├── Servers └── Repair-Windows-SQLServerusesAzureTempDrive.ps1 └── Utilities ├── Disable-Windows-FastBoot.ps ├── Download-GitHub-File.ps1 ├── Get-Linux-URLsForWebsite.sh └── Set-Windows-Branding.ps1 /Active-Directory/Add-AD-NewDomaintoMailboxes.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Adds a new email alias to all uses in a OU 4 | .DESCRIPTION 5 | Used when an Org adds a new domain to all users (e.g. in a rebrand) 6 | .INPUTS 7 | Update the Settings prior to running 8 | $OldEmailDomain: Domain to check they already have. If it doesn't exist, then nothing will be added 9 | $NewEmailDomain: New Domain to add 10 | $SearchBase: where to search for users in AD. Test by setting to a test OU only 11 | .OUTPUTS 12 | Debug information only. 13 | NB: quite a chatty debug. So comment out any info you wdon't want 14 | .NOTES 15 | File Name : Add-AD-NewDomainToAllMailboxes.sp1 16 | Author : Andrew Badge 17 | Prerequisite : Powershell 5.1 18 | #> 19 | 20 | # Update these domains to your new and old email domain names 21 | $OldEmailDomain = "@olddomain.com" 22 | $NewEmailDomain = "@newdomain.com" 23 | 24 | # Update this OU to Match your domain 25 | $SearchBase = 'OU=Users OU,DC=mydomain,DC=com' 26 | 27 | # Import the ActiveDirectory Module. 28 | # FYI: If you are running from the ActiveDirectory Powershell shell don't need this 29 | function Get-Module-ActiveDirectory 30 | { 31 | if (Get-Module -ListAvailable -Name ActiveDirectory) { 32 | Write-Host "Module exists" 33 | } 34 | else 35 | { 36 | Write-Host "Module does not exist, now trying to install module" 37 | Install-Module -Name ActiveDirectory 38 | } 39 | Import-Module ActiveDirectory 40 | } 41 | 42 | Get-Module-ActiveDirectory 43 | 44 | # Step 1 45 | # Loop all users in the Domain and Add a new Email alias if they 46 | # 1. have an email address 47 | # 2. have the old email address 48 | # NB: No filter yet as we want to inspect all users 49 | 50 | $USERS = Get-ADUser -SearchBase $SearchBase -Filter * -Properties mail,ProxyAddresses 51 | foreach ($USER in $USERS) 52 | { 53 | $Username = $USER.SamAccountName 54 | $Mail = $USER.Mail 55 | $proxies = $USER.ProxyAddresses 56 | 57 | Write-Output "Processing" $Username 58 | 59 | if (-not ([string]::IsNullOrEmpty($Mail))) 60 | { 61 | if (-not ([string]::IsNullOrEmpty($proxies))) 62 | { 63 | if ($Mail -match $OldEmailDomain) 64 | { 65 | $NewEmailAddress = $Mail.ToLower() -replace $OldEmailDomain, $NewEmailDomain 66 | 67 | if (($proxies -NotContains "smtp:$NewEmailAddress") -And ($proxies -NotContains "SMTP:$NewEmailAddress")) 68 | { 69 | 70 | Write-Output "Adding new address" $NewEmailAddress 71 | try { 72 | Set-ADUser -Identity $Username -Add @{proxyAddresses = ("smtp:" + $NewEmailAddress)} 73 | } catch { 74 | Write-Host "Exception:[$_.Exception.Message]" 75 | } 76 | } 77 | else { 78 | Write-Output "Skipping. Email Address already exists for " $Username 79 | } 80 | } 81 | else { 82 | Write-Output "Skipping. Mail attribute does contain the old domain for " $Username 83 | } 84 | } 85 | else { 86 | Write-Output "Skipping. No Email addresses for " $Username 87 | } 88 | } 89 | else { 90 | Write-Output "Skipping. Empty Mail Attribute for " $Username 91 | } 92 | } 93 | 94 | # Step 2 95 | # Loop through each user where they have a New domain's email address and set it primary 96 | 97 | $SearchFilter = "ProxyAddresses -like ""SMTP:*$NewEmailDomain""" 98 | Get-ADUser -SearchBase $SearchBase -Filter $SearchFilter -Properties mail,ProxyAddresses | 99 | Foreach { 100 | $proxies = $_.ProxyAddresses | 101 | ForEach-Object{ 102 | $a = $_ -replace 'SMTP','smtp' 103 | if($a -match $NewEmailDomain){ 104 | $a -replace 'smtp','SMTP' 105 | }else{ 106 | $a 107 | } 108 | } 109 | $_.ProxyAddresses = $proxies 110 | $_.mail = $_.mail -replace $OldEmailDomain, $NewEmailDomain 111 | 112 | Set-ADUser -instance $_ 113 | } 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Active-Directory/Clear-AD-ManagerForAllUsersInOU.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Finds user in the Retired OU that are disabled and clears the Manager 4 | .DESCRIPTION 5 | Clears the manager so the Org chart on Delves or Teams doesn't include disabled users. 6 | .INPUTS 7 | 8 | .OUTPUTS 9 | Debug Information only 10 | .NOTES 11 | File Name : Clear-AD-ManagerForAllUsersInOU.ps1 12 | Author : Andrew Badge 13 | Prerequisite : PowerShell 5.1 14 | #> 15 | 16 | #Find users in the Retired OU that are disabled 17 | $OUpath = 'ou=Retired Users,dc=orgname,dc=com,dc=au' 18 | $adusers = Get-ADUser -Filter * -SearchBase $OUpath -Property Enabled, Title, Manager | Where-Object {$_.Enabled -like “false”} 19 | 20 | #Clear the manager for disabled users in that OU 21 | Foreach ($user in $adusers) 22 | { 23 | if (![string]::IsNullOrEmpty($user.Manager)) 24 | { 25 | Write-Host (-join("Clearing manager for ",$user.Name)) 26 | $user | Set-ADUser -manager $null 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Active-Directory/Update-AD-TitleForAllUsersInOU.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Prefixes the words "Former - " in the title for all users in a OU 4 | .DESCRIPTION 5 | Intended to update old users. If their title was "Marketing Manager" and they are a retirned user update the title to "Former - Marketing Manager". 6 | Useful for new users if an old user is referred to in a permission, conversation or reference. 7 | .INPUTS 8 | 9 | .OUTPUTS 10 | Debug Information only 11 | .NOTES 12 | File Name : Update-AD-TitleForAllUsersInOU.ps1 13 | Author : Andrew Badge 14 | Prerequisite : PowerShell 5.1 15 | #> 16 | 17 | #Find users in the Retired OU that are disabled 18 | #Change the Path to suit 19 | $OUpath = 'ou=Retired Users,dc=domainname,dc=com,dc=au' 20 | $adusers = Get-ADUser -Filter * -SearchBase $OUpath -Property Enabled, Title, Manager | Where-Object {$_.Enabled -like “false”} 21 | 22 | #Add the word Former in the title for disabled users in that OU 23 | Foreach ($user in $adusers) 24 | { 25 | if (![string]::IsNullOrEmpty($user.Title)) 26 | { 27 | if (!$user.Title.StartsWith("Former - ")) 28 | { 29 | Write-Host (-join("Updating title for ",$user.Name)) 30 | $newtitle = -join("Former - ",$user.Title) 31 | Write-Host $newtitle 32 | $user | Set-ADUser -Title $newtitle 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Applications/Check-Windows-ApplicationIsInstalled.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Checks if an Application is installed 4 | .DESCRIPTION 5 | 6 | .INPUTS 7 | [string] $AppName Full Name of the App to check 8 | .OUTPUTS 9 | '$AppName' NOT is installed. 10 | '$AppName' is installed. 11 | .NOTES 12 | File Name : Check-Windows-ApplicationIsInstalled.ps1 13 | Author : Andrew Badge 14 | Prerequisite : Powershell 5.1 15 | .EXAMPLE 16 | Is-Application-Installed "Microsoft Office" 17 | #> 18 | 19 | function Is-Application-Installed { 20 | param ( 21 | [Parameter(Mandatory = $true)] 22 | [string] $AppName 23 | ) 24 | 25 | try { 26 | 27 | $installed = (Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where { $_.DisplayName -eq $AppName }) -ne $null 28 | If(-Not $installed) { 29 | $installed = (Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Where { $_.DisplayName -eq $AppName }) -ne $null 30 | } 31 | 32 | If(-Not $installed) { 33 | Write-Host "'$AppName' NOT is installed." 34 | return $false 35 | } else { 36 | Write-Host "'$AppName' is installed." 37 | return $true 38 | } 39 | 40 | } catch { 41 | Write-Host "Fatal Exception:[$_.Exception.Message]" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Applications/Create-Windows-Shortcut.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Create a shortcut for a URL or an Application 4 | .DESCRIPTION 5 | Use the .URL for websites 6 | or .LNK for aapplications 7 | .INPUTS 8 | 9 | .OUTPUTS 10 | Debug Console information only 11 | .NOTES 12 | File Name : Create-Windows-Shortcut.ps1 13 | Author : Andrew Badge 14 | Prerequisite : Powershell 5.1 15 | .EXAMPLE 16 | Create-Shortcut "https://google.com" "C:\tmp\Google.lnk","" 17 | #> 18 | 19 | function Create-Shortcut { 20 | param ( 21 | [Parameter(Mandatory = $true)] 22 | [string]$SourceExe, 23 | [Parameter(Mandatory = $true)] 24 | [string]$DestinationPath 25 | 26 | ) 27 | 28 | try { 29 | $WshShell = New-Object -comObject WScript.Shell 30 | $Shortcut = $WshShell.CreateShortcut($DestinationPath) 31 | $Shortcut.TargetPath = $SourceExe 32 | $Shortcut.Save() 33 | } catch { 34 | Write-Host "Fatal Exception:[$_.Exception.Message]" 35 | } 36 | } 37 | 38 | $BaseFolder = [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::Desktop) 39 | $ShortcutFilename = "$BaseFolder\MyGoogleURL.URL" 40 | $URL ="https://google.com.au/" 41 | 42 | Create-Shortcut $URL $ShortcutFilename 43 | -------------------------------------------------------------------------------- /Applications/Download-Windows-WordNormalTemplate.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Downloads a new normal.dotm template and copies it for the logged in user 4 | Backs up the old Normal.dotm (first Time only) 5 | .DESCRIPTION 6 | NB:This script should be run as the logged in user, not SYSTEM 7 | .INPUTS 8 | None 9 | .OUTPUTS 10 | Debug Console information only 11 | .NOTES 12 | File Name : Download-Windows-WordNormalTemplate.ps1 13 | Author : Andrew Badge 14 | Prerequisite : Powershell 5.1 15 | #> 16 | 17 | function Download-File { 18 | param ( 19 | [Parameter(Mandatory = $true)] 20 | [string] $FileURL, 21 | [Parameter(Mandatory = $true)] 22 | [string] $LocalFileName 23 | ) 24 | 25 | try { 26 | 27 | $wc = New-Object System.Net.WebClient 28 | $wc.DownloadFile($FileURL,"$LocalFileName") 29 | 30 | } catch { 31 | Write-Host "Fatal Exception:[$_.Exception.Message]" 32 | } 33 | } 34 | 35 | function Is-Account-System { 36 | try { 37 | 38 | return ([Environment]::UserName -eq "SYSTEM") 39 | 40 | } catch { 41 | Write-Host "Fatal Exception:[$_.Exception.Message]" 42 | } 43 | } 44 | 45 | try { 46 | $IsAccountSystem = Is-Account-System 47 | if (!$IsAccountSystem) { 48 | #Set intial Folder and crerate if missing 49 | $BaseFolder = "$env:ALLUSERSPROFILE\OrgName\Templates" 50 | New-Item -ItemType Directory -Force -Path $BaseFolder 51 | 52 | # Download the new template 53 | # Change the source "version" filename if you want to redownload and overwrite the existing template 54 | $URL = "https://github.com/PublicPathToTemplateFile/normal-V2021Sept7.dotm" 55 | $DownloadedFilename = "$BaseFolder\normal-V2021Sept7.dotm" 56 | if (!(Test-Path $DownloadedFilename -PathType Leaf)) { 57 | Write-Host "Downloading the new Template file" 58 | Download-File $URL $DownloadedFilename 59 | } 60 | 61 | $TemplatesFolder = "$env:APPDATA\Microsoft\Templates" 62 | $BackupFilename = "$BaseFolder\normal.dotm.backup" 63 | 64 | # Only Backup the old normal.dotm if a backup doesn't already exist 65 | if (!(Test-Path $BackupFilename -PathType Leaf)) { 66 | Write-Host "Backing up the old template" 67 | Copy-Item -Path "$TemplatesFolder\normal.dotm" -Destination $BackupFilename -Force 68 | } 69 | 70 | Write-Host "Moving new Template file to the Templates folder" 71 | Copy-Item -Path $DownloadedFilename -Destination "$TemplatesFolder\normal.dotm" -Force 72 | 73 | Write-Host "Complete." 74 | } 75 | else { 76 | Write-Host "This script must run as the Logged in User not SYSTEM. Nothing installed." 77 | } 78 | } catch { 79 | Write-Host "Fatal Exception:[$_.Exception.Message]" 80 | } -------------------------------------------------------------------------------- /Applications/Install-Windows-MicrosoftTeamsX64.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Downloads a MS Teams and installs (if not already installed) 4 | .DESCRIPTION 5 | NB: Teams installer should be run as the logged in user, not SYSTEM 6 | .INPUTS 7 | None 8 | .OUTPUTS 9 | Debug Console information only 10 | .NOTES 11 | File Name : Install-Windows-MicrosoftTeamsx64.ps1 12 | Author : Andrew Badge 13 | Prerequisite : Powershell 5.1 14 | #> 15 | 16 | function Download-File { 17 | param ( 18 | [Parameter(Mandatory = $true)] 19 | [string] $FileURL, 20 | [Parameter(Mandatory = $true)] 21 | [string] $LocalFileName 22 | ) 23 | 24 | try { 25 | 26 | $wc = New-Object System.Net.WebClient 27 | $wc.DownloadFile($FileURL,"$LocalFileName") 28 | 29 | } catch { 30 | Write-Host "Fatal Exception:[$_.Exception.Message]" 31 | } 32 | } 33 | 34 | function Is-Account-System { 35 | try { 36 | 37 | return ([Environment]::UserName -eq "SYSTEM") 38 | 39 | } catch { 40 | Write-Host "Fatal Exception:[$_.Exception.Message]" 41 | } 42 | } 43 | 44 | function Is-Application-Installed { 45 | param ( 46 | [string] $AppName 47 | ) 48 | 49 | try { 50 | 51 | $installed = (Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where { $_.DisplayName -like $AppName }) -ne $null 52 | If(-Not $installed) { 53 | $installed = (Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Where { $_.DisplayName -like $AppName }) -ne $null 54 | } 55 | 56 | If(-Not $installed) { 57 | Write-Host "'$AppName' NOT is installed." 58 | return $false 59 | } else { 60 | Write-Host "'$AppName' is installed." 61 | return $true 62 | } 63 | 64 | } catch { 65 | Write-Host "Fatal Exception:[$_.Exception.Message]" 66 | } 67 | } 68 | 69 | # Cache Folder on the local PC 70 | $BaseFolder = "$env:ALLUSERSPROFILE\ORGNAME" 71 | 72 | try { 73 | $IsAccountSystem = Is-Account-System 74 | if (!$IsAccountSystem) { 75 | $IsInstalled = Is-Application-Installed "Teams*" 76 | if (!$IsInstalled) { 77 | 78 | New-Item -ItemType Directory -Force -Path $BaseFolder 79 | 80 | if ((Get-WmiObject win32_operatingsystem | select-object osarchitecture).osarchitecture -eq "64-bit") 81 | { 82 | Write-Host "Downloading 64-bit OS version of Teams" 83 | 84 | $URL = "https://go.microsoft.com/fwlink/?linkid=859722" 85 | $File = "$BaseFolder\Teams_windows_x64.exe" 86 | Download-File $URL $File 87 | 88 | Write-Host "Installing MSTeams" 89 | & "$File" -s 90 | } 91 | else 92 | { 93 | Write-Host "This script does not run on a 32-bit OS. Nothing installed." 94 | } 95 | Write-Host "Complete." 96 | } 97 | } 98 | else { 99 | Write-Host "This script must run as the Logged in User not SYSTEM. Nothing installed." 100 | } 101 | } catch { 102 | Write-Host "Fatal Exception:[$_.Exception.Message]" 103 | } 104 | -------------------------------------------------------------------------------- /Applications/Manage-Windows-WordAutocorrectEntries.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Adds or Deletes Microsoft Word and Microsoft Outlook AutoCorrect Entries 4 | .DESCRIPTION 5 | Modify the Variables below to Add or remove owrd from the AutoCorrect entries 6 | .INPUTS 7 | None 8 | .OUTPUTS 9 | Debug Console information only 10 | .NOTES 11 | File Name : Manage-Windows-WordAutocorrectEntries.ps1 12 | Author : Andrew Badge 13 | Prerequisite : Powershell 5.1 14 | #> 15 | 16 | function Add-AutoCorrect-Entry { 17 | param ( 18 | [Parameter(Mandatory = $true)] 19 | [string] $WordToMatch, 20 | [Parameter(Mandatory = $true)] 21 | [string] $ReplaceWith 22 | ) 23 | 24 | try { 25 | 26 | $AlreadyExists = AutoCorrect-Entry-Exists $WordToMatch 27 | if ($AlreadyExists -eq $FALSE) { 28 | $word = New-Object -ComObject word.application 29 | $word.visible = $false 30 | $entries = $word.AutoCorrect.entries 31 | $entries.add($WordToMatch,$ReplaceWith) | out-null 32 | $word.Quit() 33 | $word = $null 34 | [gc]::collect() 35 | [gc]::WaitForPendingFinalizers() 36 | Write-Host "Word added." 37 | } 38 | else { 39 | Write-Host "Word already exists." 40 | } 41 | 42 | } catch { 43 | Write-Host "Fatal Exception:[$_.Exception.Message]" 44 | } 45 | } 46 | 47 | function AutoCorrect-Entry-Exists { 48 | param ( 49 | [Parameter(Mandatory = $true)] 50 | [string] $WordToMatch 51 | ) 52 | 53 | try { 54 | $WordMatched = $FALSE 55 | 56 | $word = New-Object -ComObject word.application 57 | $word.visible = $false 58 | $word.AutoCorrect.entries | 59 | Foreach { 60 | if ($_.Name -eq $WordToMatch) 61 | { 62 | $WordMatched = $TRUE 63 | } 64 | } 65 | 66 | $word.Quit() 67 | $word = $null 68 | [gc]::collect() 69 | [gc]::WaitForPendingFinalizers() 70 | 71 | 72 | } catch { 73 | Write-Host "Fatal Exception:[$_.Exception.Message]" 74 | } 75 | 76 | return $WordMatched 77 | } 78 | 79 | function Delete-AutoCorrect-Entry { 80 | param ( 81 | [Parameter(Mandatory = $true)] 82 | [string] $WordToMatch 83 | ) 84 | 85 | try { 86 | $WordMatched = $FALSE 87 | 88 | $word = New-Object -ComObject word.application 89 | $word.visible = $false 90 | $word.AutoCorrect.entries | 91 | Foreach { 92 | if ($_.Name -eq $WordToMatch) 93 | { 94 | Write-Host "Word Found. Deleting." 95 | $_.Delete() 96 | $WordMatched = $TRUE 97 | } 98 | } 99 | 100 | $word.Quit() 101 | $word = $null 102 | [gc]::collect() 103 | [gc]::WaitForPendingFinalizers() 104 | 105 | if ($WordMatched -eq $FALSE) 106 | { 107 | Write-Host "Word Not Found" 108 | } 109 | 110 | } catch { 111 | Write-Host "Fatal Exception:[$_.Exception.Message]" 112 | } 113 | } 114 | 115 | # Add the Registered Trademark Symbol to the Company Name 116 | $OldWord = "My Company" 117 | $NewWord = "My Company " + [char]::ConvertFromUtf32(0x000AE) 118 | 119 | # Checks if an Entry already exists 120 | # $AlreadyExists = AutoCorrect-Entry-Exists $OldWord 121 | # Write-Host "Aready Exists? $AlreadyExists" 122 | 123 | # Adds a word to the entries if it doesn't already exist 124 | Add-AutoCorrect-Entry $OldWord $NewWord 125 | 126 | # Deletes a word from the entries 127 | # Delete-AutoCorrect-Entry $OldWord 128 | -------------------------------------------------------------------------------- /Azure/Export-Azure-ResourceTemplatestoJSON.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Exports all Azure resources to JSON templates 4 | .DESCRIPTION 5 | Install Module from PS 6 | Install-Module -Name Az -Repository PSGallery -Force 7 | If you have issues installing the module; ensure you are running as admin. you can also try 8 | Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope Process 9 | Or install from MSI from Github 10 | https://github.com/Azure/azure-powershell/releases 11 | NB: This script will export what ever subscriptions and resources your account has access to. 12 | 13 | .INPUTS 14 | 15 | .NOTES 16 | File Name : Export-AzureResources.ps1 17 | Author : Andrew Badge 18 | Prerequisite : Powershell 5.1 19 | #> 20 | 21 | # Authenticate to Azure. You will be promoted for creds and MFA 22 | Connect-AzAccount 23 | 24 | # Create the base/root folder to export to. Update the path if required. 25 | $CurrentDate = Get-Date 26 | $BaseFolder = "c:\tmp\AzureExport-" + $CurrentDate.ToString("yyyy-MM-dd") 27 | New-Item -ItemType Directory -Force -Path $BaseFolder 28 | 29 | #Get all the subscriptions 30 | $Subscriptions = Get-AzSubscription 31 | foreach ($sub in $Subscriptions) { 32 | $SubName = $sub.Name 33 | Write-Host "--Subscription:" $SubName 34 | 35 | #Create a folder the subscriptions 36 | $SubFolder = "$BaseFolder\$SubName" 37 | New-Item -ItemType Directory -Force -Path $SubFolder 38 | 39 | #Change the context to that subscription and get all the resource groups 40 | Get-AzSubscription -SubscriptionName $SubName | Set-AzContext 41 | $ResourceGroups = Get-AzResourceGroup 42 | 43 | #Loop through each resource group 44 | #NB: you can't export the resource group in one action as there is a 200 item limit 45 | foreach ($rg in $ResourceGroups) { 46 | 47 | #Get the Resource group 48 | $RGName = $rg.ResourceGroupName 49 | Write-Host "----ResourceGroup:" $RGName 50 | 51 | #Create the folder for each RersourceGroup 52 | $RGFolder = "$SubFolder\$RGName" 53 | $RGFilename = "$RGFolder\$RGName.json" 54 | New-Item -ItemType Directory -Force -Path $RGFolder 55 | Write-Host "Exporting to:" $RGFolder 56 | 57 | #Loop through each resource in each resource group 58 | $Resources = Get-AzResource -ResourceGroupName $RGName 59 | foreach ($resource in $Resources) { 60 | #NB: some resources contain \ or / in the so this is replaced with _ 61 | $ResourceId = $resource.ResourceId 62 | $ResourceName = $resource.Name.Replace("\","_").Replace("/","_") 63 | $ErrorFile = "$RGFolder\$ResourceName.EXCEPTION" 64 | 65 | #Do the export for the resource 66 | Write-Host "------Exporting Resource:" $ResourceName 67 | try { 68 | Export-AzResourceGroup -Path "$RGFolder" -ResourceGroupName "$RGName" -Resource "$ResourceId" -WarningVariable CapturedWarning 69 | #If there is a warning, output this alongside the export JSON file 70 | if ($CapturedWarning) 71 | { 72 | $CapturedWarning | Out-File -FilePath "$RGFolder\$ResourceName.WARNING" 73 | } 74 | 75 | #Rename the export to the Resource name as the Export function always names it the ResourceGroup Name 76 | $RGNewFilename = "$RGFolder\$ResourceName.json" 77 | if (Test-Path $RGFilename -PathType leaf) { Rename-Item -Path "$RGFilename" -NewName "$RGNewFilename" } 78 | } catch { 79 | Write-Host "Fatal Exception:[$_.Exception.Message]" 80 | $_.Exception.Message | Out-File -FilePath $ErrorFile 81 | } finally { 82 | #Remove any leftover export if there is an exception so it doesn't cause any error for the next resource 83 | if (Test-Path $RGFilename -PathType leaf) { Remove-Item -Path "$RGFilename" -Force } 84 | } 85 | } 86 | 87 | Write-Host "----Export Complete for ResourceGroup:" $RGName 88 | } 89 | Write-Host "--Export Complete for Subscription:" $SubName 90 | } 91 | Write-Host "--Finished" 92 | -------------------------------------------------------------------------------- /BuyMeACoffee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewbadge/ITAdminScripts/ed52575f5bdab29f106a204537e1e66a194876df/BuyMeACoffee.png -------------------------------------------------------------------------------- /Help-and-Usage/CWAutomateScriptExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewbadge/ITAdminScripts/ed52575f5bdab29f106a204537e1e66a194876df/Help-and-Usage/CWAutomateScriptExample.png -------------------------------------------------------------------------------- /Help-and-Usage/Naming-Conventions.md: -------------------------------------------------------------------------------- 1 | # Naming Conventions 2 | 3 | This is not an exhaustive list. Mostly these are examples; Follow what is used already. If you have further examples please lert me know. 4 | 5 | ## Quick Start 6 | 7 | ``` 8 | [Verb]-[Object or Service]-[Detail] 9 | ``` 10 | 11 | 1. Proper Case 12 | 2. Dashes (-) not underscores 13 | 14 | ## Verbs 15 | 16 | * Get : Gets a single result or a list of results (table) 17 | * Set : Sets a value or setting 18 | * Remove : clears or removes a setting 19 | * Disable: Turn off or Disable a setting 20 | 21 | See (https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands?view=powershell-7.1) 22 | 23 | ## Objects or Services 24 | 25 | * O365 : Office 365 Services (Mail, Planner, Teams) 26 | * AAD : Azure AD 27 | * Windows : Windows OS 28 | * MacOS : MacOS (Der) 29 | * GoogleChrome 30 | * FireFox 31 | 32 | ## Details 33 | 34 | Short description of the action the script does. 35 | 36 | e.g. UnwantedApps, SMB1 37 | -------------------------------------------------------------------------------- /Help-and-Usage/Synopsis-Template.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | A brief description of the function or script. This keyword can be used 4 | only once in each topic. 5 | .DESCRIPTION 6 | A detailed description of the function or script. This keyword can be 7 | used only once in each topic. 8 | .INPUTS 9 | Parameters for Inputs for the function 10 | .OUTPUTS 11 | Return values or outputs of the function 12 | .NOTES 13 | File Name : xxxx.ps1 14 | Author : Name 15 | Prerequisite : List any requirements 16 | .EXAMPLE 17 | Usage: xxxx 18 | .LINK 19 | Docmentation or related Control Template 20 | https://docUrl.com 21 | #> -------------------------------------------------------------------------------- /Help-and-Usage/Using-Scripts-in-CWAutomate.md: -------------------------------------------------------------------------------- 1 | # Using scripts in ConnectWise Automate 2 | 3 | ## You need an access token 4 | Generate a Personal Acess Token from your Develper Account settings in GitHub. 5 | NB: Ideally this should be from a service account with read only access to the repository (not your user account with write access) 6 | 7 | ## Running PowerShell scripts 8 | 9 | Add two commands to your Automate script: 10 | 11 | 1. Execute Powershell 12 | 13 | ``` 14 | try { 15 | $wc = New-Object System.Net.WebClient 16 | $wc.Headers.Add('Authorization','token REPLACEWITHPERSONALTOKEN') 17 | $wc.Headers.Add('Accept','application/vnd.github.v3.raw') 18 | $wc.DownloadString('https://raw.githubusercontent.com/REPLACE/REPLACESCRIPTNAME.ps1') | iex 19 | FUNCTIONTOCALL PARAMETERS 20 | } catch { 21 | Write-Host "Fatal Exception:[$_.Exception.Message]" 22 | } 23 | ``` 24 | 25 | 2. Log the result 26 | 27 | 3. Check for an Exception. Does the returned value contain "Fatal Exception:"? 28 | 29 | ![CW Automate Script Block](https://github.com//andrewbadge/ITAdminScripts/blob/main/Help-and-Usage/CWAutomateScriptExample.png?raw=true) 30 | 31 | 32 | ## Handling errors 33 | 34 | As the code downloads the script from GitHub, common internet issues maybe block access. Noting if they have no internet then the script probably won't start either. 35 | 36 | When excuting any code expect; check the output for the string "Exception:". 37 | If this exists then consider the code failed to run. It is suggested you handle this condition at a minimum. 38 | 39 | ### The remote server returned an error: (404) Not Found. 40 | 41 | If you get this error when downloading the script from GitHub its most likely one of two things: 42 | 43 | - The Personal Access Token is incorrect. Check the value is valid. 44 | - The Script path or name is incorrect. Use the "Raw" link in GitHub to compare. 45 | 46 | ### The remote server returned an error: (403) Forbidden. 47 | 48 | If you get this error when downloading the script from GitHub; the GitHub URL could be blocked on the client. 49 | Check Umbrella or whatever URL filtering they use. 50 | 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Andrew Badge 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 | -------------------------------------------------------------------------------- /Local-Accounts/Create-Windows-LocalAdminAccount.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Creates a new local admin if it doesn't exist 4 | .DESCRIPTION 5 | Checks that the user doesn't exist then creates. 6 | the user is a member of the local Administrators group, the password does not expiry and the user cannot change the password. 7 | 8 | NB: use LAPs to manage the account's password 9 | .INPUTS 10 | [string] $Username The Username to create 11 | [string] $Description The description for the new user 12 | [securestring] $Password The password (as a secure string). See the example 13 | .OUTPUTS 14 | Logging information only 15 | .NOTES 16 | File Name : Create-Windows-LocalAdminAccount.ps1 17 | Author : Andrew Badge 18 | Prerequisite : PowerShell 5.1 19 | .EXAMPLE 20 | $NewUsername = "MyNewAdminUsername" 21 | $Description = "My Admins Description" 22 | $NewPassword = ConvertTo-SecureString 'NewComplexPassword' –asplaintext –force 23 | Create-NewLocalAdmin -Username $NewUsername -Description $Description -Password $NewPassword 24 | .LINK 25 | #> 26 | 27 | function Create-NewLocalAdmin { 28 | param ( 29 | [string] $Username, 30 | [string] $Description, 31 | [securestring] $Password 32 | ) 33 | 34 | try { 35 | $op = Get-LocalUser | Where-Object {$_.Name -eq "$Username"} 36 | if ( -not $op) { 37 | New-LocalUser "$Username" -Password $Password -FullName "$Username" -Description $Description 38 | Set-LocalUser -Name "$Username" -UserMayChangePassword $false -PasswordNeverExpires $true 39 | Write-Host "$Username local user created" 40 | Add-LocalGroupMember -Group "Administrators" -Member "$Username" 41 | Write-Host "$Username added to the local administrator group" 42 | } 43 | else { 44 | Write-Host "$Username already exists" 45 | } 46 | } catch { 47 | Write-Host "Fatal Exception:[$_.Exception.Message]" 48 | } 49 | } -------------------------------------------------------------------------------- /Local-Accounts/Disable-Windows-LocalAccount.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Disable the Local Account specified 4 | .DESCRIPTION 5 | 6 | .INPUTS 7 | [string] $Username 8 | .OUTPUTS 9 | Debug Console information only 10 | .NOTES 11 | File Name : Disable-Windows-LocalAccount.ps1 12 | Author : Andrew Badge 13 | Prerequisite : Powershell 5.1 14 | .EXAMPLE 15 | Disable-LocalAccount "OldAccountName" 16 | #> 17 | function Disable-LocalAccount { 18 | param ( 19 | [Parameter(Mandatory = $true)] 20 | [string] $Username 21 | ) 22 | 23 | try { 24 | if ($Username) { 25 | Disable-LocalUser -Name $Username 26 | } else { 27 | Write-Host "Username is blank" 28 | } 29 | } catch { 30 | Write-Host "Fatal Exception:[$_.Exception.Message]" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Local-Accounts/List-Windows-LocalAdminAccounts.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Check if users are member of the local Administrator groups 4 | .DESCRIPTION 5 | Checks each user that is enabled and a member of the local administrator group 6 | .INPUTS 7 | AdminToIgnore (String) = the username of the Local Admin to ignore. It will not be returned as an output even if detected 8 | .OUTPUTS 9 | Returns blank for no others users 10 | Otherwise Returns a list of usernames with suffixes 11 | (Local) where its a local account 12 | (AD or AAD) where its not a local account 13 | .NOTES 14 | File Name : Local-Accounts-CheckForOtherAdmins.ps1 15 | Author : Andrew Badge 16 | Prerequisite : PowerShell 5.1 17 | .EXAMPLE 18 | CheckForOtherAdmins "myAdminToExclude" 19 | .LINK 20 | #> 21 | 22 | function CheckForOtherAdmins { 23 | param ( 24 | [Parameter(Mandatory = $true)] 25 | [string]$AdminToIgnore 26 | ) 27 | try { 28 | $localadmins = @() 29 | $members = net localgroup administrators | Select-Object -skip 6 | Select-Object -skiplast 2 30 | Foreach ($member in $members) 31 | { 32 | #Find the local user. If empty then the user is not local (AD or AAD?) 33 | $localadmin = Get-LocalUser | Where-Object {$_.Name -eq $member} 34 | 35 | #If Empty then not a local Account 36 | if ($localadmin) 37 | { 38 | if ($localadmin.Enabled -eq $true) 39 | { 40 | #Skip the defined Local Admin 41 | if ($member -ne $AdminToIgnore) 42 | { 43 | #Skip the Essentials Server Built in Admin 44 | if ($member -ne "MediaAdmin$") 45 | { 46 | if ($member.EndsWith("`\Domain Admins") -eq $false) 47 | { 48 | $localadmins += $member + " (Local)" 49 | } 50 | } 51 | } 52 | } 53 | } 54 | else 55 | { 56 | if ($member.EndsWith("`\Domain Admins") -eq $false) 57 | { 58 | #Skip the Local Admin Group (assuming there is one 59 | if ($member.EndsWith("`\Desktop Local Admins") -eq $false) 60 | { 61 | $localadmins += $member + " (AD or AAD)" 62 | } 63 | } 64 | } 65 | } 66 | ($localadmins | Group-Object | Select-Object -ExpandProperty Name) -join "," 67 | } catch { 68 | Write-Host "Fatal Exception:[$_.Exception.Message]" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Office365-Mail/Enable-Office365-Mail-PlusAddressing.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Enables Plus addressing for the Office 365 Tenant 4 | .DESCRIPTION 5 | Enables Plus addressing for the whole tenant. e.g. alias+route@domain.com is delivered to the alias@domain.com mailbox 6 | .NOTES 7 | File Name : Enable-Office365-Mail-PlusAddressing.ps1 8 | Author : James Maclean 9 | Prerequisite : Office 365, Account Credentials with Exchange Administrator role active. 10 | .EXAMPLE 11 | Enable-PlusAddressing 12 | #> 13 | 14 | #Checks to see if the ExchangeOnlineManagement Module is installed, tries to install it if its not. 15 | function Get-ExchangeOnlineManagement { 16 | 17 | if (Get-Module -ListAvailable -Name ExchangeOnlineManagement) { 18 | Write-Host "Module exists" 19 | } 20 | else { 21 | Write-Host "Module does not exist, now trying to install module" 22 | Install-Module -Name ExchangeOnlineManagement 23 | } 24 | } 25 | #Connects to Exchange Online then attempts to Enable plus addressing. 26 | function Enable-PlusAddressing { 27 | try { 28 | Get-ExchangeOnlineManagment 29 | Connect-ExchangeOnline 30 | Set-OrganizationConfig -AllowPlusAddressInRecipients $true 31 | } catch { 32 | Write-Host "Fatal Exception:[$_.Exception.Message]" 33 | } 34 | finally { 35 | Disconnect-ExchangeOnline 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Office365-Sharepoint/Delete-SharePoint-OldFileVersions.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Goes through all the site, subsites and libraries and deletes old verisons of files (but not the file itself). 4 | .DESCRIPTION 5 | A popup will prompt for credentials.Ensure you login with an Admin account that has Full Access right to the SharePoint site. 6 | 7 | .INPUTS 8 | [String] $SiteUrl (Root site to Process) 9 | [String] $DataFolder (ensure the folder listed exists) 10 | 11 | .NOTES 12 | Author : Andrew Badge 13 | Prerequisite : Powershell 5.1 14 | 15 | To Tweak Settings 16 | 17 | ResumeProgress determines whether the script will always process all files and libraries. If $Falso it will progress all even if already processed 18 | VersionsToKeep (Levels 1-3) determines how many versions are retained for the OnlyFilesOlderThan (Levels 1-3) age of files (based onModified Date) 19 | 20 | Default Values: 21 | Files older than 5 Years retain 3 versions 22 | Files older than 3 Years retain 10 versions 23 | Files older than 6 Months retain 50 versions 24 | otherwise (Newer than 6 Months) retain 75 versions 25 | #> 26 | 27 | # Important settings 28 | # Duplicates the OldUsername permissions to the New Username Permissions 29 | # For the SiteURL and its SubSites and Libraries 30 | $SiteURL ="https://YourOrg.sharepoint.com/sites/YourSite/" 31 | 32 | # Folder will be created if missing 33 | # Used to store Resume file and Logs 34 | $DataFolder = "C:\Temp\SharePointDeleteOldVersions\" 35 | 36 | $DateString = (Get-Date).ToString("yyyyMMddHhmmss") 37 | $Logfile = Join-Path $DataFolder "SharePointDeleteOldVersions$DateString.log" 38 | $SiteLogfile = Join-Path $DataFolder "SharePointDeleteOldVersions.log" 39 | $StorageRecoveredfile = Join-Path $DataFolder "SharePointStorageRecovered.csv" 40 | 41 | [string[]]$global:SiteExclusionList = @() 42 | 43 | # Toggle whether Progress resumed each run after the last Site and Library you completed 44 | # Useful for very large SharePoint sites 45 | [bool]$global:ResumeProgress = $True 46 | 47 | # Toggle whether versions are deleted or just reported 48 | [bool]$global:DeleteVersions = $True 49 | 50 | # Number of Versions ot keep based on modified date 51 | # Expect Level 3 to the oldest files with Level 1 the youngest 52 | [int]$global:Default_VersionsToKeep = 75 53 | 54 | [int]$global:Level1_VersionsToKeep = 50 55 | [DateTime]$global:Level1_OnlyFilesOlderThan = (Get-Date).AddMonths(-6) 56 | 57 | [int]$global:Level2_VersionsToKeep = 10 58 | [DateTime]$global:Level2_OnlyFilesOlderThan = (Get-Date).AddYears(-3) 59 | 60 | [int]$global:Level3_VersionsToKeep = 3 61 | [DateTime]$global:Level3_OnlyFilesOlderThan = (Get-Date).AddYears(-5) 62 | 63 | # Global Flag if a Rate Limiting exception was thrown 64 | [Bool]$global:RateLimited = $False 65 | [int]$global:RateLimit_RetryAttempts = 3 66 | [int]$global:RateLimit_DelayMinutes = 60 67 | 68 | # Load PNP Powershell Module 69 | Import-Module Pnp.Powershell 70 | 71 | #region File and Log Functions 72 | 73 | Function CreateFolderIfMissing 74 | { 75 | Param ( 76 | [Parameter(Mandatory=$true)][string]$folderPath 77 | ) 78 | 79 | New-Item -ItemType Directory -Force -Path $folderPath -ErrorAction SilentlyContinue | Out-Null 80 | } 81 | 82 | Function ReadFileAsArray 83 | { 84 | Param ( 85 | [Parameter(Mandatory=$true)][string]$filePath 86 | ) 87 | 88 | If (Test-Path $filePath) { 89 | $global:SiteExclusionList = Get-Content -Path $filePath 90 | } 91 | } 92 | 93 | Function LogWrite 94 | { 95 | Param ( 96 | [Parameter(Mandatory=$true)][string]$logstring, 97 | [Parameter(Mandatory=$false)]$foreColour 98 | ) 99 | If ($null -eq $foreColour) { 100 | Write-host $logstring 101 | } else { 102 | Write-host -ForegroundColor $foreColour $logstring 103 | } 104 | 105 | Add-content $Logfile -value $logstring 106 | } 107 | 108 | Function SiteLogWrite 109 | { 110 | Param ( 111 | [Parameter(Mandatory=$true)][string]$siteurl 112 | ) 113 | 114 | Add-content $SiteLogfile -value $siteurl 115 | } 116 | 117 | Function StorageRecoveredWrite 118 | { 119 | Param ( 120 | [Parameter(Mandatory=$true)][string]$storageRecovered 121 | ) 122 | 123 | Add-content $StorageRecoveredfile -value $storageRecovered 124 | } 125 | 126 | Function IsRateLimitingException 127 | { 128 | Param ( 129 | [Parameter(Mandatory=$true)][string]$ExceptionMessage 130 | ) 131 | 132 | return ($ExceptionMessage -like "*The remote server returned an error: (429)*") 133 | } 134 | 135 | Function RateLimitDelay 136 | { 137 | Write-Progress -Activity "Rate Limit Delay" -Status "$([math]::Round($global:RateLimit_DelayMinutes,2)) Minutes remaining..." 138 | [DateTime]$DelayUntil = (Get-Date).AddMinutes($global:RateLimit_DelayMinutes) 139 | while ($DelayUntil -gt (Get-Date)) 140 | { 141 | Start-Sleep -Seconds 15 142 | 143 | $DelayRemaining = (Get-Date) - $DelayUntil 144 | Write-Progress -Activity "Rate Limit Delay" -Status "$(-[math]::Round($DelayRemaining.TotalMinutes,2)) Minutes remaining..." 145 | } 146 | } 147 | 148 | 149 | #endregion 150 | 151 | #region Site functions 152 | 153 | Function Test-AllSites{ 154 | try { 155 | 156 | $Attempt = 0 157 | $AttemptSuccess = $False 158 | 159 | while (($Attempt -lt $global:RateLimit_RetryAttempts) -and ($AttemptSuccess -eq $false)) 160 | { 161 | $Attempt++ 162 | LogWrite "Attempt $Attempt starting" Green 163 | 164 | $global:RateLimited = $False 165 | 166 | #Get All Site collections - Exclude: Seach Center, Redirect sites, Mysite Host, App Catalog, Content Type Hub, eDiscovery and Bot Sites 167 | Write-Progress -Activity "Getting the Site list" -Status "This can take a while" 168 | $AllSites = Get-PnPSubWeb -Recurse -IncludeRootWeb 169 | 170 | #Loop through each site collection 171 | ForEach($Site in $AllSites) 172 | { 173 | try { 174 | #Check if Rate Limited 175 | If ($global:RateLimited -eq $true) { break } 176 | 177 | #Chrck the Site wasn't alreayd procssed 178 | If (!$global:SiteExclusionList.Contains($Site.URL)) { 179 | Test-Site $Site.URL $oldUsername1 $oldUsername2 $newUserName 180 | } 181 | } 182 | catch { 183 | if (IsRateLimitingException $($_.Exception.Message)) { 184 | LogWrite "Rate Limited:[$_.Exception.Message]" Red 185 | $global:RateLimited = $true 186 | break 187 | } 188 | else { 189 | LogWrite "Exception:[$_.Exception.Message]" Red 190 | } 191 | } 192 | } 193 | 194 | if ($global:RateLimited -eq $true) { 195 | $AttemptSuccess = $False 196 | RateLimitDelay 197 | } 198 | else { 199 | $AttemptSuccess = $True 200 | } 201 | } 202 | } 203 | catch { 204 | LogWrite "Fatal Exception:[$_.Exception.Message]" Red 205 | } 206 | } 207 | 208 | 209 | 210 | Function Test-Site{ 211 | param ( 212 | $processSiteURL 213 | ) 214 | 215 | try { 216 | 217 | Write-Progress -Activity "Processing Site:"$processSiteURL 218 | LogWrite "Processing Site $($processSiteURL)" White 219 | 220 | #Connect to the site 221 | Connect-PnPOnline -Url $processSiteURL -Interactive 222 | 223 | $siteObject = Get-PnPWeb -Includes HasUniqueRoleAssignments,RoleAssignments 224 | 225 | #Get all document libraries from the site 226 | $DocumentLibraries = Get-PnPList -Includes HasUniqueRoleAssignments, RoleAssignments | Where-Object {$_.BaseType -eq "DocumentLibrary" -and $_.Hidden -eq $False} 227 | 228 | #Iterate through document libraries 229 | ForEach ($List in $DocumentLibraries) 230 | { 231 | #Check if Rate Limited 232 | If ($global:RateLimited -eq $true) { break } 233 | 234 | $SiteAndLibrary = "$($processSiteURL),$($List.Title)" 235 | If (!$global:SiteExclusionList.Contains($SiteAndLibrary)) { 236 | 237 | # Process all files 238 | Test-LibraryFiles $processSiteURL $List 239 | 240 | # Save progress that site and library is complete 241 | If ($global:RateLimited -eq $False) { SiteLogWrite $SiteAndLibrary } 242 | } 243 | } 244 | 245 | # Save progress that site and all libraries are complete 246 | If ($global:RateLimited -eq $False) { SiteLogWrite $processSiteURL } 247 | 248 | } 249 | catch { 250 | if (IsRateLimitingException $($_.Exception.Message)) { 251 | LogWrite "Rate Limited:[$_.Exception.Message]" Red 252 | $global:RateLimited = $true 253 | } 254 | else { 255 | LogWrite "Exception:[$_.Exception.Message]" Red 256 | } 257 | } 258 | } 259 | 260 | #endregion 261 | 262 | #region File Functions 263 | 264 | Function Test-LibraryFiles{ 265 | param ( 266 | $processSiteURL, 267 | $library 268 | ) 269 | 270 | try { 271 | 272 | Write-Progress -Activity "Processing Files in Library:[$($library.Title)]" -Status "Getting the list of files..." 273 | LogWrite "Processing Files in $($processSiteURL) [$($library.Title)]" White 274 | 275 | $Ctx= Get-PnPContext 276 | 277 | $DocumentItems = Get-PnPListItem -List $library -PageSize 500 | Where {$_.FileSystemObjectType -eq "File"} 278 | $DocumentCount = $DocumentItems.Length 279 | $DocumentNumber = 1 280 | 281 | #Loop through all documents 282 | ForEach($DocumentItem in $DocumentItems) 283 | { 284 | #Check if Rate Limited 285 | If ($global:RateLimited -eq $true) { break } 286 | 287 | $progressPercent = ($DocumentNumber/$DocumentCount)*100 288 | Write-Progress -Activity "Processing Files in Library:[$($library.Title)]" -Status $($DocumentItem["FileRef"]) -PercentComplete $progressPercent 289 | 290 | #Determine how many versions to keep based on the Modofied date 291 | 292 | $VersionToKeep = $global:Default_VersionsToKeep 293 | 294 | try { 295 | 296 | if ($DocumentItem["Modified"] -lt $global:Level3_OnlyFilesOlderThan) { 297 | $VersionToKeep = $global:Level3_VersionsToKeep 298 | } 299 | else { 300 | if ($DocumentItem["Modified"] -lt $global:Level2_OnlyFilesOlderThan) { 301 | $VersionToKeep = $global:Level2_VersionsToKeep 302 | } 303 | else { 304 | if ($DocumentItem["Modified"] -lt $global:Level1_OnlyFilesOlderThan) { 305 | $VersionToKeep = $global:Level1_VersionsToKeep 306 | } 307 | else { 308 | $VersionToKeep = 100 309 | } 310 | } 311 | } 312 | 313 | #Get File Versions 314 | $File = $DocumentItem.File 315 | $Versions = $File.Versions 316 | $Ctx.Load($File) 317 | $Ctx.Load($Versions) 318 | $Ctx.ExecuteQuery() 319 | 320 | $VersionsCount = $Versions.Count 321 | $VersionsToDelete = $VersionsCount - $VersionToKeep 322 | If($VersionsToDelete -gt 0) 323 | { 324 | LogWrite "`t $($DocumentItem["FileRef"]): Removing $($VersionsToDelete) of $($VersionsCount)" Cyan 325 | $VersionCounter= 0 326 | #Delete versions 327 | For($i=0; $i -lt $VersionsToDelete; $i++) 328 | { 329 | If($Versions[$VersionCounter].IsCurrentVersion) 330 | { 331 | $VersionCounter++ 332 | LogWrite "`t Retaining Current Major Version $($VersionCounter)" Cyan 333 | Continue 334 | } 335 | if ($global:DeleteVersions -eq $true) { 336 | #LogWrite "`t Deleting Version: $($VersionCounter)" Cyan 337 | $Versions[$VersionCounter].DeleteObject() 338 | } 339 | else { 340 | #LogWrite "`t Would have deleted Version: $($VersionCounter)" Cyan 341 | } 342 | } 343 | if ($global:DeleteVersions -eq $true) { 344 | $Ctx.ExecuteQuery() 345 | 346 | # Write the amount of data (in KB) removed 347 | # Bytes/1024 = KB * Number of Versions removed 348 | # Exported as CSV (Site,Library,File,KBRecovered) 349 | 350 | $KBRecovered = [Math]::Round(($DocumentItem["File_x0020_Size"] / 1024)*$VersionsToDelete,2) 351 | StorageRecoveredWrite """$($processSiteURL)"",""$($library.Title)"",""$($DocumentItem["FileRef"])"",$($KBRecovered),$VersionsToDelete" 352 | 353 | LogWrite "`t Updated" Cyan 354 | } 355 | else { 356 | LogWrite "`t No change" Cyan 357 | } 358 | 359 | } 360 | } catch { 361 | if (IsRateLimitingException $($_.Exception.Message)) { 362 | LogWrite "Rate Limited:[$_.Exception.Message]" Red 363 | $global:RateLimited = $true 364 | break 365 | } 366 | else { 367 | LogWrite "Exception:[$_.Exception.Message]" Red 368 | } 369 | } 370 | 371 | $DocumentNumber++ 372 | } 373 | 374 | } catch { 375 | if (IsRateLimitingException $($_.Exception.Message)) { 376 | LogWrite "Rate Limited:[$_.Exception.Message]" Red 377 | $global:RateLimited = $true 378 | } 379 | else { 380 | LogWrite "Exception:[$_.Exception.Message]" Red 381 | } 382 | } 383 | } 384 | 385 | #endregion 386 | 387 | Clear-Host 388 | 389 | LogWrite "-----Script Started $($DateString)" Green 390 | SiteLogWrite "-----Script Started $($DateString)" 391 | 392 | # Connect to Sharepoint Site 393 | Write-Progress -Activity "Logging in" 394 | Connect-PnPOnline -URL $SiteURL -Interactive 395 | 396 | #init folders and read exclusions 397 | CreateFolderIfMissing $DataFolder 398 | If ($global:ResumeProgress) { ReadFileAsArray $SiteLogfile } 399 | 400 | #Iterate the sites 401 | Test-AllSites 402 | 403 | Write-Progress -Completed -Activity " " 404 | LogWrite "-----Script Complete" Green 405 | SiteLogWrite "-----Script Complete $($DateString)" -------------------------------------------------------------------------------- /Office365-Sharepoint/Replace-SharePoint-Permission.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Goes through all the site, subsites and libraries and adds a new group with permissions to match the existing permissions of an old group 4 | .DESCRIPTION 5 | A popup will prompt for credentials.Ensure you login with an Admin account that has Full rights to your SharePoint site. 6 | 7 | .INPUTS 8 | [String] $SiteUrl (Root site to Process) 9 | [String] $OldUserName1 (Copies the permissions if the old username has permissions) 10 | [String] $OldUserName2 (Copies the permissions if the old username has permissions) 11 | [String] $NewUserName (Username to create with the copied permissions) 12 | 13 | [String] $Logfile (ensure the folder listed exists) 14 | .NOTES 15 | Author : Andrew Badge 16 | Prerequisite : Powershell 5.1 17 | #> 18 | 19 | # Important settings 20 | # Duplicates the OldUsername permissions to the New Username Permissions 21 | # For the SiteURL and its SubSites and Libraries 22 | $SiteURL ="https://YourOrg.sharepoint.com/sites/YourSite/YourSubsite" 23 | # Change with your Group names (AD or SharePoint) 24 | $OldUserName1 = "Old Group A" 25 | $OldUserName2 = "Old Group B" 26 | $NewUserName = "Replace with thgis Group" 27 | 28 | # Folder will be created if missing 29 | # Used to store Resume file and Logs 30 | $DataFolder = "C:\Temp\" 31 | 32 | $DateString = (Get-Date).ToString("yyyyMMddHhmmss") 33 | $Logfile = Join-Path $DataFolder "SharePointPermissionUpdate$DateString.log" 34 | $SiteLogfile = Join-Path $DataFolder "SharePointPermissionSites.log" 35 | 36 | [string[]]$global:SiteExclusionList = @() 37 | 38 | # Toggle whether Progress resumed each run after the last Site and Library you completed 39 | # Useful for very large SharePoint sites 40 | [bool]$global:ResumeProgress = $True 41 | 42 | # Toggle what objects are checked 43 | [bool]$global:ProcessLibraries = $True 44 | #NB: you must process Libraries if you want to process Folders or Files 45 | [bool]$global:ProcessFolders = $True 46 | [bool]$global:ProcessFiles = $True 47 | 48 | # Load PNP Powershell Module 49 | Import-Module Pnp.Powershell 50 | 51 | #region File and Log Functions 52 | 53 | Function CreateFolderIfMissing 54 | { 55 | Param ( 56 | [Parameter(Mandatory=$true)][string]$folderPath 57 | ) 58 | 59 | New-Item -ItemType Directory -Force -Path $folderPath -ErrorAction SilentlyContinue | Out-Null 60 | } 61 | 62 | Function ReadFileAsArray 63 | { 64 | Param ( 65 | [Parameter(Mandatory=$true)][string]$filePath 66 | ) 67 | 68 | If (Test-Path $filePath) { 69 | $global:SiteExclusionList = Get-Content -Path $filePath 70 | } 71 | } 72 | 73 | Function LogWrite 74 | { 75 | Param ( 76 | [Parameter(Mandatory=$true)][string]$logstring, 77 | [Parameter(Mandatory=$false)]$foreColour 78 | ) 79 | If ($null -eq $foreColour) { 80 | Write-host $logstring 81 | } else { 82 | Write-host -ForegroundColor $foreColour $logstring 83 | } 84 | 85 | Add-content $Logfile -value $logstring 86 | } 87 | 88 | Function SiteLogWrite 89 | { 90 | Param ( 91 | [Parameter(Mandatory=$true)][string]$siteurl 92 | ) 93 | 94 | Add-content $SiteLogfile -value $siteurl 95 | } 96 | 97 | #endregion 98 | 99 | #region Site functions 100 | 101 | Function Test-AllSites{ 102 | param ( 103 | $oldUsername1, 104 | $oldUsername2, 105 | $newUsername 106 | ) 107 | try { 108 | Write-Progress -Activity "Getting the Site list" -Status "This can take a while" 109 | 110 | #Get All Site collections - Exclude: Seach Center, Redirect sites, Mysite Host, App Catalog, Content Type Hub, eDiscovery and Bot Sites 111 | $AllSites = Get-PnPSubWeb -Recurse -IncludeRootWeb 112 | 113 | #Loop through each site collection 114 | ForEach($Site in $AllSites) 115 | { 116 | Try { 117 | 118 | If (!$global:SiteExclusionList.Contains($Site.URL)) { 119 | #LogWrite "Site To be processed $($Site.URL)" Green 120 | Test-Site $Site.URL $oldUsername1 $oldUsername2 $newUserName 121 | } 122 | else { 123 | #LogWrite "Site Excluded $($Site.URL)" Gray 124 | } 125 | } 126 | Catch { 127 | write-host "Error: $($_.Exception.Message)" -foregroundcolor Red 128 | } 129 | } 130 | } 131 | catch { 132 | Write-Host "Fatal Exception:[$_.Exception.Message]" 133 | } 134 | } 135 | 136 | Function Test-SiteRoleAssignmentExists{ 137 | param ( 138 | $roleAssignments, 139 | $newUsername 140 | ) 141 | 142 | [bool]$assignmentExists = $False 143 | 144 | try { 145 | 146 | foreach($siteRole in $roleAssignments) { 147 | Get-PnPProperty -ClientObject $siteRole -Property RoleDefinitionBindings, Member 148 | 149 | $PermissionType = $siteRole.Member.PrincipalType 150 | $PermissionLevels = $siteRole.RoleDefinitionBindings | Select -ExpandProperty Name 151 | 152 | If ($PermissionLevels.Length -eq 0) { Continue } 153 | 154 | If ($PermissionType -eq "SecurityGroup") { 155 | 156 | If (($siteRole.Member.Title -eq $newUsername) -and ($PermissionLevels -ne "Limited Access") -and ($PermissionLevels -ne "Web-Only Limited Access") -and ($PermissionLevels -ne "Limited Access, Web-Only Limited Access")) 157 | { 158 | $assignmentExists = $True 159 | } 160 | } 161 | } 162 | } 163 | catch { 164 | Write-Host "Fatal Exception:[$_.Exception.Message]" 165 | } 166 | return $assignmentExists 167 | } 168 | 169 | 170 | Function Test-Site{ 171 | param ( 172 | $processSiteURL, 173 | $oldUsername1, 174 | $oldUsername2, 175 | $newUsername 176 | ) 177 | 178 | try { 179 | 180 | Write-Progress -Activity "Processing Site:"$processSiteURL 181 | 182 | #Connect to the site 183 | Connect-PnPOnline -Url $processSiteURL -Interactive 184 | 185 | $siteObject = Get-PnPWeb -Includes HasUniqueRoleAssignments,RoleAssignments 186 | If($siteObject.HasUniqueRoleAssignments) 187 | { 188 | If ($siteObject.RoleAssignments.Length -eq 0) { 189 | LogWrite "Site role assigments for: $($processSiteURL) [$($List.Title)] are empty" Red 190 | } 191 | 192 | If (Test-SiteRoleAssignmentExists $siteObject.RoleAssignments $newUsername) 193 | { 194 | LogWrite "Site: $($processSiteURL) permissions already exist" Yellow 195 | } 196 | else { 197 | 198 | [bool]$usersFound = $false 199 | [int]$validPermissionCount = 0 200 | 201 | foreach($siteRole in $siteObject.RoleAssignments) { 202 | Get-PnPProperty -ClientObject $siteRole -Property RoleDefinitionBindings, Member 203 | 204 | $PermissionType = $siteRole.Member.PrincipalType 205 | $PermissionLevels = $siteRole.RoleDefinitionBindings | Select -ExpandProperty Name 206 | 207 | If ($PermissionLevels.Length -eq 0) { 208 | LogWrite "Site: $($processSiteURL) permissions are empty" Red 209 | } 210 | else 211 | { 212 | If (($PermissionLevels -ne "Limited Access") -and ($PermissionLevels -ne "Web-Only Limited Access") -and ($PermissionLevels -ne "Limited Access, Web-Only Limited Access")) 213 | { 214 | $validPermissionCount++ 215 | #Debug 216 | #LogWrite "Site: $($processSiteURL) Role: $($siteRole.Member.Title) Level: $($PermissionLevels)" White 217 | 218 | 219 | 220 | If (($siteRole.Member.Title -eq $oldUsername1) -or ($siteRole.Member.Title -eq $oldUsername2)) 221 | { 222 | $usersFound = $true 223 | 224 | If ($PermissionType -eq "SharePointGroup") { 225 | LogWrite "Site permission for: $($processSiteURL) was a SharePoint group. Review." Red 226 | } 227 | 228 | [bool]$permissionSet = $false 229 | [string]$newPermission = "" 230 | 231 | $newPermission = "Contribute but not Delete" 232 | If ($PermissionLevels.Contains($newPermission) -and ($permissionSet -eq $false)) { 233 | LogWrite "Fixing Site permission for: $($processSiteURL) with $($newPermission) permissions" Green 234 | Set-PnPWebPermission -User $newUsername -AddRole $newPermission 235 | } 236 | 237 | $newPermission = "Contribute" 238 | If ($PermissionLevels.Contains($newPermission) -and ($permissionSet -eq $false)) { 239 | LogWrite "Fixing Site permission for: $($processSiteURL) with $($newPermission) permissions" Green 240 | Set-PnPWebPermission -User $newUsername -AddRole $newPermission 241 | } 242 | 243 | $newPermission = "Read" 244 | If ($PermissionLevels.Contains($newPermission) -and ($permissionSet -eq $false)) { 245 | LogWrite "Fixing Site permission for: $($processSiteURL) with $($newPermission) permissions" Green 246 | Set-PnPWebPermission -User $newUsername -AddRole $newPermission 247 | } 248 | 249 | If ($permissionSet -eq $false) { 250 | LogWrite "Site: $($processSiteURL) has other permissions $($PermissionLevels)" Red 251 | } 252 | } 253 | } 254 | } 255 | } 256 | 257 | If ($usersFound -eq $false) { 258 | LogWrite "Site: $($processSiteURL) old users didn't have permission" Red 259 | } 260 | 261 | If ($validPermissionCount -eq 0) { 262 | LogWrite " Fixing Site permission for: $($processSiteURL). No valid permissions. Reset to Inherit" Green 263 | $Context = Get-PnPContext 264 | $siteObject.ResetRoleInheritance() 265 | $siteObject.update() 266 | $Context.ExecuteQuery() 267 | $permissionSet = $True 268 | } 269 | } 270 | 271 | } else { 272 | LogWrite "Site: $($processSiteURL) has inherited permissions" Gray 273 | } 274 | 275 | If ($global:ProcessLibraries -eq $True) 276 | { 277 | #Get all document libraries from the site 278 | $DocumentLibraries = Get-PnPList -Includes HasUniqueRoleAssignments, RoleAssignments | Where-Object {$_.BaseType -eq "DocumentLibrary" -and $_.Hidden -eq $False} 279 | 280 | #Iterate through document libraries 281 | ForEach ($List in $DocumentLibraries) 282 | { 283 | $SiteAndLibrary = "$($processSiteURL),$($List.Title)" 284 | If (!$global:SiteExclusionList.Contains($SiteAndLibrary)) { 285 | 286 | If ($List.RoleAssignments.Length -eq 0) { 287 | LogWrite " Library role assigments for: $($processSiteURL) [$($List.Title)] are empty" Red 288 | } 289 | 290 | If($List.HasUniqueRoleAssignments) 291 | { 292 | If (Test-LibraryRoleAssignmentExists $List.RoleAssignments $newUsername) 293 | { 294 | LogWrite " Library: $($processSiteURL) [$($List.Title)] permissions already exist" Yellow 295 | } 296 | else 297 | { 298 | [bool]$usersFound = $false 299 | [int]$validPermissionCount = 0 300 | 301 | Foreach ($RoleAssignment in $List.RoleAssignments) { 302 | Get-PnPProperty -ClientObject $RoleAssignment -Property RoleDefinitionBindings, Member 303 | 304 | $PermissionType = $RoleAssignment.Member.PrincipalType 305 | $PermissionLevels = $RoleAssignment.RoleDefinitionBindings | Select -ExpandProperty Name 306 | 307 | #Debug 308 | #LogWrite " Library: $($processSiteURL) [$($List.Title)] Role: $($RoleAssignment.Member.Title) Level: $($PermissionLevels)" White 309 | 310 | 311 | 312 | If ($PermissionLevels.Length -eq 0) { 313 | LogWrite " Library permission for: $($processSiteURL) [$($List.Title)] are empty" Red 314 | } 315 | else 316 | { 317 | If (($PermissionLevels -ne "Limited Access") -and ($PermissionLevels -ne "Web-Only Limited Access") -and ($PermissionLevels -ne "Limited Access, Web-Only Limited Access")) 318 | { 319 | $validPermissionCount++ 320 | 321 | If (($RoleAssignment.Member.Title -eq $oldUsername1) -or ($RoleAssignment.Member.Title -eq $oldUsername2)) 322 | { 323 | $usersFound = $true 324 | 325 | If ($PermissionType -eq "SharePointGroup") { 326 | LogWrite " Library permission for: $($processSiteURL) [$($List.Title)] was a SharePoint group" Red 327 | } 328 | 329 | [bool]$permissionSet = $false 330 | [string]$newPermission = "" 331 | 332 | $newPermission = "Contribute but not Delete" 333 | If ($PermissionLevels.Contains($newPermission) -and ($permissionSet -eq $false)) { 334 | LogWrite " Fixing Library permission for: $($processSiteURL) [$($List.Title)] with $($newPermission) permissions" Green 335 | Set-LibraryRole $list.Title $newUsername $newPermission 336 | } 337 | 338 | $newPermission = "Contribute" 339 | If ($PermissionLevels.Contains($newPermission) -and ($permissionSet -eq $false)) { 340 | LogWrite " Fixing Library permission for: $($processSiteURL) [$($List.Title)] with $($newPermission) permissions" Green 341 | Set-LibraryRole $list.Title $newUsername $newPermission 342 | } 343 | 344 | $newPermission = "Read" 345 | If ($PermissionLevels.Contains($newPermission) -and ($permissionSet -eq $false)) { 346 | LogWrite " Fixing Library permission for: $($processSiteURL) [$($List.Title)] with $($newPermission) permissions" Green 347 | Set-LibraryRole $list.Title $newUsername $newPermission 348 | } 349 | 350 | If ($permissionSet -eq $false) { 351 | LogWrite " Library: $($processSiteURL) [$($List.Title)] has other permissions $($PermissionLevels)" Red 352 | } 353 | } 354 | } 355 | } 356 | 357 | } 358 | 359 | If ($usersFound -eq $false) { 360 | LogWrite " Library: $($processSiteURL) [$($List.Title)] old users didn't have permission" Red 361 | } 362 | 363 | If ($validPermissionCount -eq 0) { 364 | LogWrite " Fixing Library permission for: $($processSiteURL) [$($List.Title)]. No valid permissions. Reset to Inherit" Green 365 | #Set-PnPListPermission -Identity $($List.Title) -InheritPermissions 366 | $Context = Get-PnPContext 367 | $List.ResetRoleInheritance() 368 | $List.update() 369 | $Context.ExecuteQuery() 370 | $permissionSet = $True 371 | } 372 | } 373 | } else { 374 | LogWrite " Library: $($processSiteURL) [$($List.Title)] has inherited permissions" Gray 375 | } 376 | 377 | # Process all folders 378 | If ($global:ProcessFolders -eq $True) { 379 | Test-LibraryFolders $List $oldUsername1 $oldUsername2 $newUsername 380 | } 381 | 382 | # Process all files 383 | If ($global:ProcessFiles -eq $True) { 384 | Test-LibraryFiles $List $oldUsername1 $oldUsername2 $newUsername 385 | } 386 | 387 | # Save progress that site and library is complete 388 | SiteLogWrite $SiteAndLibrary 389 | } 390 | } 391 | } 392 | 393 | # Save progress that site and all libraries are complete 394 | SiteLogWrite $processSiteURL 395 | 396 | } 397 | catch { 398 | Write-Host "Fatal Exception:[$_.Exception.Message]" 399 | } 400 | } 401 | 402 | #endregion 403 | 404 | #region Library functions 405 | 406 | Function Test-LibraryRoleAssignmentExists{ 407 | param ( 408 | $roleAssignments, 409 | $newUsername 410 | ) 411 | 412 | [bool]$assignmentExists = $False 413 | 414 | try { 415 | 416 | Foreach ($RoleAssignment in $List.RoleAssignments) { 417 | Get-PnPProperty -ClientObject $RoleAssignment -Property RoleDefinitionBindings, Member 418 | 419 | $PermissionType = $RoleAssignment.Member.PrincipalType 420 | $PermissionLevels = $RoleAssignment.RoleDefinitionBindings | Select -ExpandProperty Name 421 | 422 | If ($PermissionLevels.Length -eq 0) { Continue } 423 | 424 | If ($PermissionType -eq "SecurityGroup") { 425 | 426 | If (($RoleAssignment.Member.Title -eq $newUsername) -and ($PermissionLevels -ne "Limited Access") -and ($PermissionLevels -ne "Web-Only Limited Access") -and ($PermissionLevels -ne "Limited Access, Web-Only Limited Access")) 427 | { 428 | $assignmentExists = $True 429 | } 430 | } 431 | } 432 | } 433 | catch { 434 | Write-Host "Fatal Exception:[$_.Exception.Message]" 435 | } 436 | return $assignmentExists 437 | } 438 | 439 | Function Set-LibraryRole{ 440 | param ( 441 | $listTitle, 442 | $userName, 443 | $permissionLevel 444 | ) 445 | 446 | try { 447 | Set-PnPListPermission -Identity $listTitle -User $userName -AddRole $permissionLevel 448 | } 449 | catch { 450 | Write-Host "Fatal Exception:[$_.Exception.Message]" 451 | } 452 | } 453 | 454 | #endregion 455 | 456 | #region Folder Functions 457 | 458 | Function Test-LibraryFolders{ 459 | param ( 460 | $library, 461 | $oldUsername1, 462 | $oldUsername2, 463 | $newUsername 464 | ) 465 | 466 | [bool]$oldUserExists = $False 467 | [bool]$newUserExists = $False 468 | [string]$olduserPermissionLevel = "" 469 | 470 | try { 471 | 472 | Write-Progress -Activity "Processing Folders in Library:[$($library.Title)]" -Status "Getting the list of folders..." 473 | 474 | $FolderObjects = Get-PnPListItem -List $library -PageSize 500 | Where {$_.FileSystemObjectType -eq "Folder"} 475 | $FolderCount = $FolderObjects.Length 476 | $FolderIndex = 1 477 | 478 | #Loop through all documents 479 | ForEach($FolderObject in $FolderObjects) 480 | { 481 | $progressPercent = ($FolderIndex/$FolderCount)*100 482 | Write-Progress -Activity "Processing Folder in Library:[$($library.Title)]" -Status $($FolderObject["FileRef"]) -PercentComplete $progressPercent 483 | 484 | $oldUserExists = $False 485 | $newUserExists = $False 486 | 487 | $folder = Get-PnPFolder -Url $FolderObject["FileRef"] -Includes ListItemAllFields.HasUniqueRoleAssignments, ListItemAllFields.RoleAssignments 488 | #Get-PnPProperty -ClientObject $folder -Property ListItemAllFields.HasUniqueRoleAssignments, ListItemAllFields.RoleAssignments 489 | 490 | #Only do anything if not Inheritted 491 | if($folder.ListItemAllFields.HasUniqueRoleAssignments -eq $True) 492 | { 493 | [int]$validPermissionCount = 0 494 | LogWrite " Folder: [$($FolderObject["FileRef"])] has unique permissions" Yellow 495 | 496 | #Check for Existing Permissions 497 | foreach($RoleAssignment in $folder.ListItemAllFields.RoleAssignments ) 498 | { 499 | Get-PnPProperty -ClientObject $RoleAssignment -Property RoleDefinitionBindings, Member 500 | 501 | #$PermissionType = $RoleAssignment.Member.PrincipalType 502 | $PermissionLevels = $RoleAssignment.RoleDefinitionBindings | Select -ExpandProperty Name 503 | 504 | #Ignore Limtied access permissions 505 | If (($PermissionLevels -ne "Limited Access") -and ($PermissionLevels -ne "Web-Only Limited Access") -and ($PermissionLevels -ne "Limited Access, Web-Only Limited Access")) 506 | { 507 | $validPermissionCount++ 508 | 509 | If (($RoleAssignment.Member.Title -eq $newUsername)) 510 | { 511 | $newUserExists = $true 512 | } 513 | 514 | If (($RoleAssignment.Member.Title -eq $oldUsername1)) 515 | { 516 | $oldUserExists = $true 517 | $olduserPermissionLevel = $PermissionLevels 518 | } 519 | } 520 | } 521 | 522 | [bool]$permissionSet = $false 523 | If (($newUserExists -eq $False) -and ($oldUserExists -eq $True)) 524 | { 525 | [string]$newPermission = "" 526 | 527 | $newPermission = "Contribute but not Delete" 528 | If ($olduserPermissionLevel.Contains($newPermission) -and ($permissionSet -eq $false)) { 529 | LogWrite " Folder: [$($FolderObject["FileRef"])] permissions set to $($newPermission)" Green 530 | Set-PnPFolderPermission -List $library.Title -Identity $FolderObject["FileRef"] -User $newUsername -AddRole $newPermission 531 | $permissionSet = $True 532 | } 533 | 534 | $newPermission = "Contribute" 535 | If ($olduserPermissionLevel.Contains($newPermission) -and ($permissionSet -eq $false)) { 536 | LogWrite " Folder: [$($FolderObject["FileRef"])] permissions set to $($newPermission)" Green 537 | Set-PnPFolderPermission -List $library.Title -Identity $FolderObject["FileRef"] -User $newUsername -AddRole $newPermission 538 | $permissionSet = $True 539 | } 540 | 541 | $newPermission = "Read" 542 | If ($olduserPermissionLevel.Contains($newPermission) -and ($permissionSet -eq $false)) { 543 | LogWrite " Folder: [$($FolderObject["FileRef"])] permissions set to $($newPermission)" Green 544 | Set-PnPFolderPermission -List $library.Title -Identity $FolderObject["FileRef"] -User $newUsername -AddRole $newPermission 545 | $permissionSet = $True 546 | } 547 | 548 | If ($permissionSet -eq $false) { 549 | LogWrite " Folder: [$($FolderObject["FileRef"])] has other permissions $($olduserPermissionLevel)" Red 550 | } 551 | } 552 | 553 | #Fix permissions where they are empty. Reset to Inherit 554 | If (($validPermissionCount -eq 0) -and ($permissionSet -eq $false)) { 555 | LogWrite " Folder: [$($FolderObject["FileRef"])] No valid permissions. Reset to Inherit." Green 556 | Set-PnPFolderPermission -List $library.Title -Identity $FolderObject["FileRef"] -InheritPermissions 557 | $permissionSet = $True 558 | } 559 | 560 | } 561 | else { 562 | #LogWrite " Folder: [$($FolderObject["Title"])] has inherited permissions" Green 563 | } 564 | 565 | 566 | $FolderIndex++ 567 | } 568 | 569 | } 570 | catch { 571 | Write-Host "Fatal Exception:[$_.Exception.Message]" 572 | } 573 | return $assignmentExists 574 | } 575 | 576 | #endregion 577 | 578 | #region File Functions 579 | 580 | Function Test-LibraryFiles{ 581 | param ( 582 | $library, 583 | $oldUsername1, 584 | $oldUsername2, 585 | $newUsername 586 | ) 587 | 588 | [bool]$oldUserExists = $False 589 | [bool]$newUserExists = $False 590 | [string]$olduserPermissionLevel = "" 591 | 592 | try { 593 | 594 | Write-Progress -Activity "Processing Files in Library:[$($library.Title)]" -Status "Getting the list of files..." 595 | 596 | $DocumentItems = Get-PnPListItem -List $library -PageSize 500 | Where {$_.FileSystemObjectType -eq "File"} 597 | $DocumentCount = $DocumentItems.Length 598 | $DocumentNumber = 1 599 | 600 | #Loop through all documents 601 | ForEach($DocumentItem in $DocumentItems) 602 | { 603 | $progressPercent = ($DocumentNumber/$DocumentCount)*100 604 | Write-Progress -Activity "Processing Files in Library:[$($library.Title)]" -Status $($DocumentItem["FileRef"]) -PercentComplete $progressPercent 605 | 606 | $oldUserExists = $False 607 | $newUserExists = $False 608 | 609 | $file = Get-PnPFile -Url $DocumentItem["FileRef"] -AsListItem 610 | Get-PnPProperty -ClientObject $file -Property HasUniqueRoleAssignments, RoleAssignments 611 | 612 | #Only do anything if not Inheritted 613 | if($file.HasUniqueRoleAssignments -eq $True) 614 | { 615 | [int]$validPermissionCount = 0 616 | LogWrite " File: [$($DocumentItem["FileRef"])] has unique permissions" Yellow 617 | 618 | #Check for Existing Permissions 619 | foreach($RoleAssignment in $file.RoleAssignments ) 620 | { 621 | Get-PnPProperty -ClientObject $RoleAssignment -Property RoleDefinitionBindings, Member 622 | 623 | #$PermissionType = $RoleAssignment.Member.PrincipalType 624 | $PermissionLevels = $RoleAssignment.RoleDefinitionBindings | Select -ExpandProperty Name 625 | 626 | #Ignore Limtied access permissions 627 | If (($PermissionLevels -ne "Limited Access") -and ($PermissionLevels -ne "Web-Only Limited Access") -and ($PermissionLevels -ne "Limited Access, Web-Only Limited Access")) 628 | { 629 | $validPermissionCount++ 630 | 631 | If (($RoleAssignment.Member.Title -eq $newUsername)) 632 | { 633 | $newUserExists = $true 634 | } 635 | 636 | If (($RoleAssignment.Member.Title -eq $oldUsername1)) 637 | { 638 | $oldUserExists = $true 639 | $olduserPermissionLevel = $PermissionLevels 640 | } 641 | } 642 | } 643 | 644 | [bool]$permissionSet = $false 645 | If (($newUserExists -eq $False) -and ($oldUserExists -eq $True)) 646 | { 647 | [string]$newPermission = "" 648 | 649 | $newPermission = "Contribute but not Delete" 650 | If ($olduserPermissionLevel.Contains($newPermission) -and ($permissionSet -eq $false)) { 651 | LogWrite " File: [$($DocumentItem["FileRef"])] permissions set to $($newPermission)" Green 652 | Set-PnPListItemPermission -List $library -Identity $file -User $newUsername -AddRole $newPermission 653 | $permissionSet = $True 654 | } 655 | 656 | $newPermission = "Contribute" 657 | If ($olduserPermissionLevel.Contains($newPermission) -and ($permissionSet -eq $false)) { 658 | LogWrite " File: [$($DocumentItem["FileRef"])] permissions set to $($newPermission)" Green 659 | Set-PnPListItemPermission -List $library -Identity $file -User $newUsername -AddRole $newPermission 660 | $permissionSet = $True 661 | } 662 | 663 | $newPermission = "Read" 664 | If ($olduserPermissionLevel.Contains($newPermission) -and ($permissionSet -eq $false)) { 665 | LogWrite " File: [$($DocumentItem["FileRef"])] permissions set to $($newPermission)" Green 666 | Set-PnPListItemPermission -List $library -Identity $file -User $newUsername -AddRole $newPermission 667 | $permissionSet = $True 668 | } 669 | 670 | If ($permissionSet -eq $false) { 671 | LogWrite " File: [$($DocumentItem["FileRef"])] has other permissions $($olduserPermissionLevel)" Red 672 | } 673 | } 674 | 675 | #Fix permissions where they are empty. Reset to Inherit 676 | If (($validPermissionCount -eq 0) -and ($permissionSet -eq $false)) { 677 | LogWrite " File: [$($DocumentItem["FileRef"])] No valid permissions. Reset to Inherit." Green 678 | Set-PnPListItemPermission -List $library -Identity $file -InheritPermissions 679 | $permissionSet = $True 680 | } 681 | 682 | } 683 | else { 684 | #LogWrite " File: [$($DocumentItem["Title"])] has inherited permissions" Green 685 | } 686 | 687 | 688 | $DocumentNumber++ 689 | } 690 | 691 | } 692 | catch { 693 | Write-Host "Fatal Exception:[$_.Exception.Message]" 694 | } 695 | return $assignmentExists 696 | } 697 | 698 | #endregion 699 | 700 | Clear-Host 701 | 702 | LogWrite "-----Script Started $($DateString)" Green 703 | SiteLogWrite "-----Script Started $($DateString)" 704 | 705 | # Connect to Sharepoint Site 706 | Write-Progress -Activity "Logging in" 707 | Connect-PnPOnline -URL $SiteURL -Interactive 708 | 709 | #init folders and read exclusions 710 | CreateFolderIfMissing $DataFolder 711 | If ($global:ResumeProgress) { ReadFileAsArray $SiteLogfile } 712 | 713 | #Iterate the sites 714 | Test-AllSites $OldUserName1 $OldUserName2 $NewUserName 715 | 716 | Write-Progress -Completed -Activity " " 717 | LogWrite "-----Script Complete" Green 718 | SiteLogWrite "-----Script Complete $($DateString)" -------------------------------------------------------------------------------- /Office365-Sharepoint/Update-SharePoint-SiteLogos.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Update the root site and subsites to the logo specified 4 | .DESCRIPTION 5 | Creds supplied will need to be a site wonerr for all sites 6 | .INPUTS 7 | [String] $SiteUrl (Root site to Process) 8 | [String] $LogoURL (Logo Path to Set) 9 | .NOTES 10 | File Name : Update-SharePointSiteLogos.ps1 11 | Author : Andrew Badge 12 | Prerequisite : Powershell 5.1 13 | Thanks and Reference: https://www.sharepointdiary.com/2016/11/sharepoint-online-how-to-change-logo-using-powershell.html 14 | #> 15 | 16 | #Add PowerShell Module for SharePoint Online 17 | Import-Module Microsoft.Online.SharePoint.Powershell -DisableNameChecking 18 | 19 | ##Configuration variables. Change these to suit 20 | $SiteUrl = "https://orgname.sharepoint.com/sites/sitename" 21 | $LogoURL="/sites/sitename/Style Library/Logos/Logo.png" 22 | 23 | $Cred = Get-Credential 24 | $Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Cred.Username, $Cred.Password) 25 | 26 | 27 | Try { 28 | 29 | #Setup the context 30 | $Ctx = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl) 31 | $Ctx.Credentials = $Credentials 32 | 33 | #Get the Root web 34 | $Web = $Ctx.Web 35 | $Ctx.Load($Web) 36 | $Ctx.ExecuteQuery() 37 | 38 | #Function to change Logo for the given web 39 | Function Update-Logo($Web) 40 | { 41 | try { 42 | #Update Logo 43 | $Web.SiteLogoUrl = $LogoURL 44 | $Web.Update() 45 | $Ctx.ExecuteQuery() 46 | Write-host "Updated Logo for Web:" $Web.URL 47 | 48 | #Process each subsite in the site 49 | $Subsites = $Web.Webs 50 | $Ctx.Load($Subsites) 51 | $Ctx.ExecuteQuery() 52 | Foreach ($SubSite in $Subsites) 53 | { 54 | #Call the function Recursively 55 | Update-Logo($Subsite) 56 | } 57 | } 58 | Catch { 59 | write-host -f Red "Error updating Logo for " $Web.URL $_.Exception.Message 60 | } 61 | } 62 | 63 | #Call the function to change the logo of the web 64 | Update-Logo($Web) 65 | } 66 | Catch { 67 | write-host -f Red "Error updating Logo!" $_.Exception.Message 68 | } 69 | -------------------------------------------------------------------------------- /Onboarding/Remove-Windows-UnwantedApps.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Removes Windows 10 App installed by default that are unwanted. 4 | .DESCRIPTION 5 | Removed unwanted apps, games and trialware from Windows 10. 6 | .NOTES 7 | File Name : Windows10-RemoveUnwantedApps.ps1 8 | Author : Andrew Badge 9 | Prerequisite : Windows 10 10 | .LINK 11 | 12 | #> 13 | function RemoveUnwantedApps { 14 | try { 15 | Write-Host "Starting to remove unwanted apps" 16 | 17 | $bloatware = @( 18 | "*3DBuilder*" 19 | "*Bing*" 20 | "*CandyCrush*" 21 | "*DellInc.DellDigitalDelivery*" 22 | "*Facebook*" 23 | "*feedbackhub*" 24 | "*freshpaint*" 25 | "*king.com*" 26 | "*Linkedin*" 27 | "*Microsoft.Messaging*" 28 | "*Microsoft.MsixPackagingTool*" 29 | "*Microsoft.OneConnect*" 30 | "*Microsoft.People*" 31 | "*Microsoft.YourPhone*" 32 | "*Microsoft3DViewer*" 33 | "*MixedReality*" 34 | "*Netflix*" 35 | "*Office*" 36 | "*print3D*" 37 | "*Sketchable*" 38 | "*Solitaire*" 39 | "*soundrecorder*" 40 | "*Spotify*" 41 | "*Twitter*" 42 | "*wallet*" 43 | "*windowsalarms*" 44 | "*windowscommunicationsapps*" 45 | "*Windowsphone*" 46 | "*xbox*" 47 | "*xboxapp*" 48 | "*xboxgameoverlay*" 49 | "*Zune*" 50 | ) 51 | 52 | foreach ($bloat in $bloatware) { 53 | if (($app = Get-AppxPackage -AllUsers $bloat) -and ($app.Name -notlike "*XboxGameCallableUI*")) { 54 | Write-Host "$($app.Name) app found. Uninstalling..." 55 | try { 56 | Write-Progress -CurrentOperation "$($app.Name) app found. Uninstalling..." -Activity "Uninstalling" 57 | $app | Remove-AppxPackage -allusers -EA Stop 58 | } catch { 59 | Write-Host "Uninstall of $($app.Name) failed. Error is:" 60 | Write-Host $_.Exception.Message 61 | } 62 | } 63 | if ($provapp = Get-AppxProvisionedPackage -Online | Where-Object {$_.DisplayName -like $bloat}) { 64 | Write-Host "$($provapp.DisplayName) provisioned app found. Uninstalling..." 65 | try { 66 | Write-Progress -CurrentOperation "$($provapp.DisplayName) provisioned app found. Uninstalling..." -Activity "Uninstalling" 67 | $provapp | Remove-AppxProvisionedPackage -Online -EA Stop 68 | } catch { 69 | Write-Host "Uninstall of $($provapp.DisplayName) failed. Error is:" 70 | Write-Host $_.Exception.Message 71 | } 72 | } 73 | } 74 | 75 | Write-Host "Complete" 76 | } catch { 77 | Write-Host "Fatal Exception:[$_.Exception.Message]" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Patching/Get-Windows-MissingUpdates.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Lists all missing Windows Updates 4 | .DESCRIPTION 5 | Lists missing updates as a table 6 | .OUTPUTS 7 | Returns a table of updates including the Title, whether its downloaded, hidden etc 8 | .NOTES 9 | File Name : Get-Windows-MissingUpdates.ps1 10 | Author : Andrew Badge 11 | Prerequisite : PowerShell 5.1 12 | .EXAMPLE 13 | Get-MissingUpdates 14 | .LINK 15 | #> 16 | 17 | function Get-MissingUpdates { 18 | try { 19 | #List all missing updates 20 | Write-Output "Creating Microsoft.Update.Session COM object" 21 | $session1 = New-Object -ComObject Microsoft.Update.Session -ErrorAction silentlycontinue 22 | 23 | Write-Output "Creating Update searcher" 24 | $searcher = $session1.CreateUpdateSearcher() 25 | 26 | Write-Output "Searching for missing updates..." 27 | $result = $searcher.Search("IsInstalled=0") 28 | 29 | #Updates are waiting to be installed 30 | $updates = $result.Updates; 31 | 32 | Write-Output "Found $($updates.Count) updates!" 33 | $updates | Format-Table Title, AutoSelectOnWebSites, IsDownloaded, IsHiden, IsInstalled, IsMandatory, IsPresent, AutoSelection, AutoDownload -AutoSize 34 | } catch { 35 | Write-Host "Fatal Exception:[$_.Exception.Message]" 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IT Admin Scripts 2 | Scripts for IT Admins for standalone use or from within RMM or other management tools such as Intune, ConnectWise Automate or NinjaRMM. 3 | 4 | In general these scripts are Windows focussed hence mostly Powershell. 5 | 6 | If you find this useful and would like me to continue working on it please [![Buy Me a Coffee](https://github.com/andrewbadge/DNSRoaming/blob/main/Images/BuyMeACoffee.png)](https://www.buymeacoffee.com/AndrewBadge). 7 | 8 | ## Repo Rules (for myself) 9 | 10 | **No secrets in the repository** 11 | No passwords, keys, secrets of any sort. Nothing should be sensitive. 12 | Use parameters instead! 13 | 14 | **No PC, client or user information** 15 | Try not to use PC names, client names, or specific detail related to clients or users in scripts. 16 | Make the usage generic or use parameters if possible! 17 | 18 | ## Code and Content 19 | The preference is all scripts are: 20 | - Powershell for Windows (.ps1) 21 | - Bash for MacOS (.bash) 22 | 23 | Windows Command (.bat) files are also acceptible. 24 | 25 | **All Code should include a Synopsis block** 26 | 27 | See the [Synopsis template here](https://github.com/andrewbadge/ITAdminScripts/blob/main/Help-and-Usage/Synopsis-Template.ps1) 28 | 29 | **Naming** 30 | 31 | See [Naming Conventions](https://github.com/andrewbadge/ITAdminScripts/blob/main/Help-and-Usage/Naming-Conventions.md) 32 | 33 | ## Usage 34 | 35 | [Using Scripts in ConnectWise Automate](https://github.com/andrewbadge/ITAdminScripts/blob/main/Help-and-Usage/Using-Scripts-in-CWAutomate.md) 36 | 37 | # Licensing 38 | 39 | See [License](https://github.com/andrewbadge/ITAdminScripts/blob/main/LICENSE) 40 | 41 | # Thanks 42 | 43 | This started as a private repo for my own use with code from my own snippets and previous jobs. I'm sure 50% of this code has been copied from StackExchange or GitHub then rehashed. Thanks to everyone who continue to share. 44 | 45 | Thanks to James Maclean for at least one script and previous help! Send me more James. 46 | 47 | If there is something not correctly atrributed then please let me know and I'll fix it. 48 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Reporting a Vulnerability 2 | Email DNSRoaming@outlook.com if you would like to report a Vulnerability 3 | -------------------------------------------------------------------------------- /Security-Hardening/Disable-Quick-Assist.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Disables the Windows Quick Assist functionality. 4 | .DESCRIPTION 5 | Checks to see if Quick assist is enabled using Get-WindowsCapability. If enabled it well then disable it. 6 | .NOTES 7 | File Name : Disable-Quick-Assist.ps1 8 | Author : Andrew Badge 9 | Prerequisite : Powershell 5.1 10 | .EXAMPLE 11 | Remove-Quickassist 12 | #> 13 | 14 | 15 | function Remove-Quickassist { 16 | $Quickassist=(get-WindowsCapability -online -name App.Support.QuickAssist~~~~0.0.1.0 | select-object -ExpandProperty state) 17 | if ($Quickassist -eq 'Installed') { 18 | Remove-WindowsCapability -online -name App.Support.QuickAssist~~~~0.0.1.0 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Security-Hardening/Disable-Windows-SMB1.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Disable the SMB1 protocol on a Windows machine 4 | .DESCRIPTION 5 | Used to disable SMBv1, this should be ran on demand or immediately in most case to assure SMBv1 is disabled 6 | .NOTES 7 | File Name : Disable-SMB1.ps1 8 | Author : Andrew Badge 9 | Prerequisite : Server 2008 R2, Windows 8 or later 10 | .LINK 11 | #> 12 | 13 | function Disable-SMB1 { 14 | try { 15 | Write-Host "Disabling SMB1" 16 | Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters" SMB1 -Type DWORD -Value 0 -Force 17 | Write-Host "Complete" 18 | } catch { 19 | Write-Host "Fatal Exception:[$_.Exception.Message]" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Security-Hardening/Enable-Quick-Assist.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Enables the Windows Quick Assist functionality. 4 | .DESCRIPTION 5 | Enables Quick assist using Add-WindowsCapability. When running this script you should remember to disable it afterwards. 6 | .NOTES 7 | File Name : Enable-Quick-Assist.ps1 8 | Author : Andrew Badge 9 | Prerequisite : Powershell 5.1 10 | .EXAMPLE 11 | Add-Quickassist 12 | #> 13 | 14 | 15 | function Add-Quickassist { 16 | try { 17 | Add-WindowsCapability -online -name App.Support.QuickAssist~~~~0.0.1.0 18 | } catch { 19 | Write-Host "Fatal Exception:[$_.Exception.Message]" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Servers/Repair-Windows-SQLServerusesAzureTempDrive.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Gives "Everyone" Full Control of an Azure Temporary Drive and ensures the service is started. 4 | It is intended to be run as a script on server start. 5 | .DESCRIPTION 6 | If an Azure Server has runs SQL Server and the Azure Temporary drive is used for SQL TempDB 7 | storage; then the SQL Server service may not start after a reboot as the drive doesn't have permissions (the Temp Drive is recreated on reboot). 8 | 9 | This may occur if the SQL Service account Network Service account and can't write the drive by default. 10 | This script will give Everyone access and restart the service. 11 | .INPUTS 12 | [string] $DriveLetter, 13 | [string] $ServiceName 14 | .OUTPUTS 15 | Debug Console information only 16 | .NOTES 17 | File Name : Repair-Windows-SQLServerusesAzureTempDrive.ps1 18 | Author : Andrew Badge 19 | Prerequisite : Powershell 5.1 20 | .EXAMPLE 21 | $Logfile = "C:\tmp\Repair-Windows-SQLServerusesAzureTempDrive.log" 22 | $DriveToFix = "D:" 23 | $ServiceName = "MSSQL`$InstanceName" 24 | 25 | Repair-Windows-SQLServerusesAzureTempDrive $DriveToFix $ServiceName 26 | #> 27 | 28 | 29 | Function LogWrite 30 | { 31 | Param ([string]$logstring) 32 | 33 | Write-Host $logstring 34 | 35 | $CurrentDate = Get-Date 36 | $CurrentDateString = $CurrentDate.ToString("yyyy-MM-dd HH:mm:ss") 37 | 38 | Add-content $Logfile -value "$CurrentDateString $logstring" 39 | } 40 | 41 | function Set-Folder-EveryoneAccess { 42 | param ( 43 | [String] $Folder 44 | ) 45 | 46 | try { 47 | $Acl = Get-Acl $Folder 48 | $Ar = New-Object System.Security.AccessControl.FileSystemAccessRule("Everyone", "FullControl","ContainerInherit, ObjectInherit", "InheritOnly", "Allow") 49 | $Acl.SetAccessRule($Ar) 50 | Set-Acl $Folder $Acl 51 | 52 | } catch { 53 | Write-Host "Fatal Exception:[$_.Exception.Message]" 54 | } 55 | } 56 | 57 | function IsServiceRunning { 58 | param ( 59 | [String] $ServiceName 60 | ) 61 | 62 | try { 63 | $arrService = Get-Service -Name $ServiceName 64 | 65 | return ($arrService.Status -eq 'Running') 66 | } catch { 67 | Write-Host "Fatal Exception:[$_.Exception.Message]" 68 | return $false 69 | } 70 | } 71 | 72 | function Restart-ServiceIfStopped { 73 | param ( 74 | [String] $ServiceName 75 | ) 76 | 77 | try { 78 | $arrService = Get-Service -Name $ServiceName 79 | 80 | if ($arrService.Status -ne 'Running') 81 | { 82 | 83 | Start-Service $ServiceName 84 | LogWrite "Service status is" $arrService.status 85 | LogWrite 'Service starting' 86 | Start-Sleep -seconds 60 87 | $arrService.Refresh() 88 | if ($arrService.Status -eq 'Running') 89 | { 90 | LogWrite 'Service is now running' 91 | } 92 | else { 93 | LogWrite 'Service failed to start' 94 | Throw 'Service failed to start' 95 | } 96 | } 97 | else { 98 | LogWrite 'Service is already running' 99 | } 100 | } catch { 101 | Write-Host "Fatal Exception:[$_.Exception.Message]" 102 | } 103 | } 104 | function Repair-Windows-SQLServerusesAzureTempDrive { 105 | param ( 106 | [string] $DriveLetter, 107 | [string] $ServiceName 108 | ) 109 | 110 | try { 111 | 112 | $ServiceIsRunning = IsServiceRunning $ServiceName 113 | if (!$ServiceIsRunning) 114 | { 115 | Set-Folder-EveryoneAccess $DriveLetter 116 | Start-Sleep -seconds 20 117 | Restart-ServiceIfStopped $ServiceName 118 | } 119 | else { 120 | LogWrite 'Service is already running' 121 | } 122 | 123 | } catch { 124 | Write-Host "Fatal Exception:[$_.Exception.Message]" 125 | } 126 | } 127 | 128 | $Logfile = "C:\tmp\Validate-ServiceStartsforTempDrive.log" 129 | $DriveToFix = "D:" 130 | $ServiceName = "MSSQL`$InstanceName" 131 | 132 | Repair-Windows-SQLServerusesAzureTempDrive $DriveToFix $ServiceName 133 | -------------------------------------------------------------------------------- /Utilities/Disable-Windows-FastBoot.ps: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Disables FastBoot so Windows 10 really shuts down. 4 | .DESCRIPTION 5 | Avoid issues related to not really restarting. 6 | .NOTES 7 | File Name : Disable-Windows-FastBoot.ps1 8 | Author : Andrew Badge 9 | Prerequisite : Powershell 5.1 10 | .EXAMPLE 11 | Disable-Windows-FastBoot 12 | #> 13 | 14 | function Disable-Windows-FastBoot { 15 | try { 16 | REG ADD "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Power" /v HiberbootEnabled /t REG_DWORD /d "0" /f 17 | } catch { 18 | Write-Host "Fatal Exception:[$_.Exception.Message]" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Utilities/Download-GitHub-File.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Downloads a file from a private GitHub Repo to a local file 4 | .DESCRIPTION 5 | 6 | .INPUTS 7 | [string] $GitHubPersonalAccessToken, 8 | [string] $GitHubFileURL, 9 | [string] $LocalFileName 10 | .NOTES 11 | File Name : Download-GitHub-File.ps1 12 | Author : Andrew Badge 13 | Prerequisite : Powershell 5.1 14 | .EXAMPLE 15 | $Token = 'token ABC123456789' 16 | $URL = 'https://raw.githubusercontent.com/Account/REPO/Branch/Path/Filename.txt' 17 | $File = 'C:\tmp\Filename.txt' 18 | Download-GitHub-File $Token $URL $File 19 | #> 20 | 21 | function Download-GitHub-File { 22 | param ( 23 | [string] $GitHubPersonalAccessToken, 24 | [string] $GitHubFileURL, 25 | [string] $LocalFileName 26 | ) 27 | 28 | try { 29 | 30 | try { 31 | $wc = New-Object System.Net.WebClient 32 | $wc.Headers.Add('Authorization',$GitHubPersonalAccessToken) 33 | $wc.Headers.Add('Accept','application/vnd.github.v4.raw') 34 | $wc.DownloadFile($GitHubFileURL,"$LocalFileName") 35 | } catch { 36 | Write-Host "Fatal Exception:[$_.Exception.Message]" 37 | } 38 | 39 | 40 | } catch { 41 | Write-Host "Fatal Exception:[$_.Exception.Message]" 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Utilities/Get-Linux-URLsForWebsite.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #SYNOPSIS 3 | # Gets all the URLs a website references 4 | #DESCRIPTION 5 | # Gets all the URLs a website references; images, AD tracking URLs, APIs 6 | # Useful to determine address to unblock in Umbrella or if a website has been compromised (Coinminer injected etc). 7 | #NOTES 8 | # File Name : Get-Linux-URLsForWebsite.sh 9 | # Author : Andrew Badge 10 | # Prerequisite : pcregrep: use "sudo apt install pcregrep" to install 11 | #EXAMPLE 12 | # Get-URLs "https://www.andrewbadge.com" 13 | # 14 | 15 | Get-URLs() 16 | { 17 | WEBADDRESS=$1 18 | curl -s $WEBADDRESS | pcregrep -o "(http:\/\/|https:\/\/).*?(?=\"|'| )" | sort -u 19 | } 20 | -------------------------------------------------------------------------------- /Utilities/Set-Windows-Branding.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Set the Windows Wallpaper and lockscreen 4 | .DESCRIPTION 5 | 6 | .INPUTS 7 | [String] $LockScreenSource 8 | [String] $BackgroundSource 9 | .NOTES 10 | File Name : Set-Windows-Branding.ps1 11 | Author : Andrew Badge 12 | Prerequisite : Powershell 5.1 13 | .EXAMPLE 14 | Set-Windows-Branding 15 | #> 16 | 17 | function Set-Windows-Branding { 18 | param ( 19 | [String] $LockScreenSource, 20 | [String] $BackgroundSource 21 | ) 22 | 23 | try { 24 | $RegKeyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\PersonalizationCSP" 25 | $DesktopPath = "DesktopImagePath" 26 | $DesktopStatus = "DesktopImageStatus" 27 | $DesktopUrl = "DesktopImageUrl" 28 | $LockScreenPath = "LockScreenImagePath" 29 | $LockScreenStatus = "LockScreenImageStatus" 30 | $LockScreenUrl = "LockScreenImageUrl" 31 | 32 | $StatusValue = "1" 33 | $DesktopImageValue = "C:\Windows\System32\oobe\Desktop.jpg" 34 | $LockScreenImageValue = "C:\Windows\System32\oobe\LockScreen.jpg" 35 | 36 | if(!(Test-Path $RegKeyPath)) { 37 | Write-Host "Creating registry path $($RegKeyPath)." 38 | New-Item -Path $RegKeyPath -Force | Out-Null 39 | } 40 | if ($LockScreenSource) { 41 | Write-Host "Copy Lock Screen image from $($LockScreenSource) to $($LockScreenImageValue)." 42 | #(New-Object System.Net.WebClient).DownloadFile($LockScreenSource, "$LockScreenImageValue") 43 | copy-item $LockScreenSource "$LockScreenImageValue" 44 | Write-Host "Creating registry entries for Lock Screen" 45 | New-ItemProperty -Path $RegKeyPath -Name $LockScreenStatus -Value $StatusValue -PropertyType DWORD -Force | Out-Null 46 | New-ItemProperty -Path $RegKeyPath -Name $LockScreenPath -Value $LockScreenImageValue -PropertyType STRING -Force | Out-Null 47 | New-ItemProperty -Path $RegKeyPath -Name $LockScreenUrl -Value $LockScreenImageValue -PropertyType STRING -Force | Out-Null 48 | } 49 | if ($BackgroundSource) { 50 | Write-Host "Copy Desktop Background image from $($BackgroundSource) to $($DesktopImageValue)." 51 | #(New-Object System.Net.WebClient).DownloadFile($BackgroundSource, "$DesktopImageValue") 52 | copy-item $BackgroundSource "$DesktopImageValue" 53 | Write-Host "Creating registry entries for Desktop Background" 54 | New-ItemProperty -Path $RegKeyPath -Name $DesktopStatus -Value $StatusValue -PropertyType DWORD -Force | Out-Null 55 | New-ItemProperty -Path $RegKeyPath -Name $DesktopPath -Value $DesktopImageValue -PropertyType STRING -Force | Out-Null 56 | New-ItemProperty -Path $RegKeyPath -Name $DesktopUrl -Value $DesktopImageValue -PropertyType STRING -Force | Out-Null 57 | } 58 | 59 | } catch { 60 | Write-Host "Fatal Exception:[$_.Exception.Message]" 61 | } 62 | } 63 | 64 | $LockScreenSource = "c:\tmp\lockscreen.jpg" 65 | $BackgroundSource = "c:\tmp\wallpaper.jpg" 66 | Set-Windows-Branding $LockScreenSource $BackgroundSource --------------------------------------------------------------------------------