├── ADV190023 ├── ActiveDirectory-DCLDAPEvents.ps1 ├── ActiveDirectory-LDAPInterfaceEventLogging.ps1 ├── GPO │ └── {1739C31B-397E-4E1C-B914-60BB91266283} │ │ ├── Backup.xml │ │ ├── DomainSysvol │ │ └── GPO │ │ │ └── Machine │ │ │ ├── comment.cmtx │ │ │ ├── microsoft │ │ │ └── windows nt │ │ │ │ └── SecEdit │ │ │ │ └── GptTmpl.inf │ │ │ └── registry.pol │ │ ├── bkupInfo.xml │ │ └── gpreport.xml └── README.md ├── Active Directory ├── Microsoft-ADAccountExpiry.ps1 ├── Microsoft-ADFilterUserToGroup.ps1 ├── Microsoft-CheckFixDomainMembership.ps1 ├── Microsoft-CleanADGroupMembers.ps1 ├── Microsoft-CopyADGroupMembers.ps1 └── README.md ├── FSLogix ├── FSLogix-DiffDiskToUniqueDisk.ps1 ├── FSLogix-DiskSize.ps1 ├── FSLogix-ExpandDisk.ps1 ├── FSLogix-LocalProfileToProfileContainer.ps1 ├── FSLogix-MigrateFromSIDToUsernameFolder.ps1 ├── FSLogix-MigrateFromUsernameToSIDFolder.ps1 ├── FSLogix-MoveContainerOfNotExistingUser.ps1 ├── FSLogix-MoveContainerToGPT.ps1 ├── FSLogix-OfficeContainerSizeWarning.ps1 ├── FSLogix-ProfileContainerSizeWarning.ps1 ├── FSLogix-ProfileDiskToProfileContainer.ps1 ├── FSLogix-RemoveContainerData.ps1 ├── FSLogix-RemoveDirtyBit.ps1 ├── FSLogix-ShowMountedVolumes.ps1 ├── FSLogix-ShrinkDisk.ps1 ├── FSLogix-UPMToProfileContainer.ps1 ├── FSLogixMigration │ ├── FSLogixMigration.psd1 │ ├── FSLogixMigration.psm1 │ ├── Helper Functions │ │ ├── Copy-Profile.ps1 │ │ ├── Get-ProfileSource.ps1 │ │ ├── Mount-UPDProfile.ps1 │ │ ├── New-MigrationObject.ps1 │ │ ├── New-ProfileDisk.ps1 │ │ ├── New-ProfileReg.ps1 │ │ └── Write-Log.ps1 │ ├── Main Functions │ │ ├── Convert-RoamingProfile.ps1 │ │ ├── Convert-UPDProfile.ps1 │ │ └── Convert-UPMProfile.ps1 │ ├── PesterRunTests.ps1 │ ├── README.md │ └── Tests │ │ ├── Convert-RoamingProfile.Test.ps1 │ │ ├── Convert-UPDProfile.Test.ps1 │ │ ├── Copy-Profile.Test.ps1 │ │ ├── FSLogixMigration.Test.ps1 │ │ ├── Generate-RandomFiles.ps1 │ │ ├── Get-ProfileSource.Test.ps1 │ │ ├── Mount-UPDProfile.Test.ps1 │ │ ├── New-MigrationObject.Test.ps1 │ │ ├── New-ProfileDisk.Test.ps1 │ │ ├── New-ProfileReg.Test.ps1 │ │ └── Write-Log.Test.ps1 ├── Microsoft-StateRepositoryReset.ps1 ├── Office365-MailboxSize.ps1 ├── README.md ├── targets_ODFC.xml ├── targets_Profile.xml └── user.xml ├── MSIX App Attach ├── 0-PreStage.ps1 ├── 1-stage.ps1 ├── 2-register.ps1 ├── 3-deregister.ps1 ├── 4-destage.ps1 ├── AppAttach.ps1 └── README.md ├── OneDrive ├── Microsoft-ConfigureStorageSense.ps1 ├── OneDrive-CopyFromFolderRedirectionToOneDrive.ps1 └── OneDrive-SetFilesOnDemandToCloudOnly.ps1 ├── README.md ├── SQL ├── Microsoft-SQLAccountExpiry.ps1 └── README.md ├── Teams ├── README.md ├── Teams-AutostartResetCU.ps1 ├── Teams-AutostartResetLM.ps1 ├── Teams-ClearCache.ps1 ├── Teams-FindVulnerable.ps1 ├── Teams-LogoffUserSettings.ps1 ├── Teams-MachineBasedCleanUp.ps1 ├── Teams-UserBasedCleanUp.ps1 └── Teams-UserSettings.ps1 └── Windows ├── MCT ├── auto.cmd ├── latest_MCT_script.url └── no_11_setup_checks_on_dynamic_update.cmd ├── Microsoft-Autorun.ps1 ├── Microsoft-CheckStartProcesses.ps1 ├── Microsoft-DeleteOldFiles.ps1 ├── Microsoft-DeleteProfileForGroupMember.ps1 ├── Microsoft-EventAggregator.ps1 ├── Microsoft-GroupMembershipModifier.ps1 ├── Microsoft-InfoViaCIM.ps1 ├── Microsoft-InstalledSoftware.ps1 ├── Microsoft-KillProcess.ps1 ├── Microsoft-LocalProfileCleaner.ps1 ├── Microsoft-LocalRedirectedFolder.ps1 ├── Microsoft-LogonFTA.ps1 ├── Microsoft-LogonFTA ├── Adobe Acrobat Reader.txt └── Adobe Acrobat.txt ├── Microsoft-MissingUpdates.ps1 ├── Microsoft-OptimizeDefaultUserProfile.ps1 ├── Microsoft-PendingReboot.ps1 ├── Microsoft-ProfileCleaner.ps1 ├── Microsoft-RemoteUserLogonTime.ps1 ├── Microsoft-ResetStartmenu.ps1 ├── Microsoft-SetNewUserProfile.ps1 ├── Microsoft-StartUpCopyFTA.ps1 ├── Microsoft-StartUpCopyFTA └── FileAssociation2019.xml ├── Microsoft-UserSessions.ps1 ├── Microsoft-Windows10MediaCreationTool.bat └── README.md /ADV190023/ActiveDirectory-DCLDAPEvents.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script checks all domain controller concerning Event ID 2887 & 3040 (LDAP Clear Text/Unsigned) 4 | 5 | .DESCRIPTION 6 | This script checks all domain controllers of the domain via Get-WinEvent and searches for the event with the 7 | ID 2887 in the Directory Service Event Log. If this script finds domain controllers where the event occurs, a 8 | CSV file is created in the directory ~\Documents and after checking all domain controllers a table is displayed in the shell. 9 | 10 | .NOTES 11 | Version: 1.0 12 | Author: Arne Tiedemann 13 | Rewrite Author: Manuel Winkel 14 | Creation Date: 2020-03-04 15 | Purpose/Change: Extension by event id 3040 16 | #> 17 | ########################################################################### 18 | # Variables 19 | ########################################################################### 20 | $OU = (Get-ADDomain).DomainControllersContainer 21 | $DCs = Get-ADComputer -Filter * -SearchBase $OU 22 | 23 | # Create an Array to hold our returnedvValues 24 | $InsecureLDAPCount = @() 25 | ########################################################################### 26 | # Functions 27 | ########################################################################### 28 | 29 | ########################################################################### 30 | # Script 31 | ########################################################################### 32 | # CleanUp the Environment 33 | $null = Get-ChildItem -Path ~\Documents\InsecureLDAPBinds*.csv | Remove-Item -Force 34 | 35 | # Getting Events from all DCs 36 | foreach($DC in $DCs.Name) { 37 | if (Test-Connection -Count 1 -ComputerName $DC -ErrorAction SilentlyContinue) { 38 | # Define the result as true 39 | $Result = $true 40 | 41 | # Test if Machine is avalable 42 | try { 43 | $null = Test-WSMan -ComputerName $DC -ErrorAction Stop 44 | } catch { 45 | Write-Host 'We are not able to use remote management with these Server: ' -NoNewline 46 | Write-Host $DC -ForegroundColor Yellow 47 | $Result = $false 48 | } 49 | 50 | # Run only if the machine is reachable 51 | if ($Result -eq $true) { 52 | try { 53 | Write-Host 'Getting Events 2887 from DC: ' -NoNewline 54 | Write-Host $DC -ForegroundColor Green -NoNewline 55 | 56 | # Grab the appropriate event entries 57 | $Events = Get-WinEvent -ComputerName $DC -FilterHashtable @{Logname='Directory Service';Id=2887; StartTime=(Get-Date).AddHours(-24)} -ErrorAction SilentlyContinue 58 | 59 | if ($Events.Count -gt 0) { 60 | 61 | # Loop through each event and output the 62 | ForEach ($Event in $Events) { 63 | $eventXML = [xml]$Event.ToXml() 64 | 65 | # Build Our Values 66 | $Count = ($eventXML.event.EventData.Data[0]) 67 | } 68 | 69 | # Add new line to Arraylist 70 | $InsecureLDAPCount += [pscustomobject]@{ 71 | DomainController = $DC 72 | Count = $Count 73 | } 74 | } 75 | Write-Host ' Done...' -ForegroundColor Yellow 76 | } catch {} 77 | try { 78 | Write-Host 'Getting Events 3040 from DC: ' -NoNewline 79 | Write-Host $DC -ForegroundColor Green -NoNewline 80 | 81 | # Grab the appropriate event entries 82 | $Events = Get-WinEvent -ComputerName $DC -FilterHashtable @{Logname='Directory Service';Id=3040; StartTime=(Get-Date).AddHours(-24)} -ErrorAction SilentlyContinue 83 | 84 | 85 | if ($Events.Count -gt 0) { 86 | 87 | # Loop through each event and output the 88 | ForEach ($Event in $Events) { 89 | $eventXML = [xml]$Event.ToXml() 90 | 91 | # Build Our Values 92 | $Count = ($eventXML.event.EventData.Data[0]) 93 | } 94 | 95 | # Add new line to Arraylist 96 | $InsecureLDAPCount += [pscustomobject]@{ 97 | DomainController = $DC 98 | Count = $Count 99 | } 100 | } 101 | Write-Host ' Done...' -ForegroundColor Yellow 102 | } catch {} 103 | } 104 | } 105 | } 106 | 107 | # Dump it all out to a CSV. 108 | if($InsecureLDAPCount.Count -gt 0) { 109 | $InsecureLDAPCount | Export-CSV -NoTypeInformation ~\Documents\InsecureLDAPCount.csv 110 | } 111 | 112 | ########################################################################### 113 | # Finally 114 | ########################################################################### 115 | $InsecureLDAPCount | Where-Object { $_.Count -gt 0 } | Format-Table -AutoSize 116 | ########################################################################### 117 | # End 118 | ########################################################################### 119 | -------------------------------------------------------------------------------- /ADV190023/GPO/{1739C31B-397E-4E1C-B914-60BB91266283}/Backup.xml: -------------------------------------------------------------------------------- 1 | 2 | 01 00 04 9c 00 00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 04 00 ec 00 08 00 00 00 05 02 28 00 00 01 00 00 01 00 00 00 8f fd ac ed b3 ff d1 11 b4 1d 00 a0 c9 68 f9 39 01 01 00 00 00 00 00 05 0b 00 00 00 00 00 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 39 93 bc 3c fd 2e 8c 5b 58 8c b1 4d 00 02 00 00 00 02 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 39 93 bc 3c fd 2e 8c 5b 58 8c b1 4d 00 02 00 00 00 02 24 00 ff 00 0f 00 01 05 00 00 00 00 00 05 15 00 00 00 39 93 bc 3c fd 2e 8c 5b 58 8c b1 4d 07 02 00 00 00 02 14 00 94 00 02 00 01 01 00 00 00 00 00 05 09 00 00 00 00 02 14 00 94 00 02 00 01 01 00 00 00 00 00 05 0b 00 00 00 00 02 14 00 ff 00 0f 00 01 01 00 00 00 00 00 05 12 00 00 00 00 0a 14 00 ff 00 0f 00 01 01 00 00 00 00 00 03 00 00 00 00 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ADV190023/GPO/{1739C31B-397E-4E1C-B914-60BB91266283}/DomainSysvol/GPO/Machine/comment.cmtx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ADV190023/GPO/{1739C31B-397E-4E1C-B914-60BB91266283}/DomainSysvol/GPO/Machine/microsoft/windows nt/SecEdit/GptTmpl.inf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deyda/Microsoft/e99a4ddaab36d1d82b69b0843f650a88e71dfbb8/ADV190023/GPO/{1739C31B-397E-4E1C-B914-60BB91266283}/DomainSysvol/GPO/Machine/microsoft/windows nt/SecEdit/GptTmpl.inf -------------------------------------------------------------------------------- /ADV190023/GPO/{1739C31B-397E-4E1C-B914-60BB91266283}/DomainSysvol/GPO/Machine/registry.pol: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deyda/Microsoft/e99a4ddaab36d1d82b69b0843f650a88e71dfbb8/ADV190023/GPO/{1739C31B-397E-4E1C-B914-60BB91266283}/DomainSysvol/GPO/Machine/registry.pol -------------------------------------------------------------------------------- /ADV190023/GPO/{1739C31B-397E-4E1C-B914-60BB91266283}/bkupInfo.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ADV190023/GPO/{1739C31B-397E-4E1C-B914-60BB91266283}/gpreport.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deyda/Microsoft/e99a4ddaab36d1d82b69b0843f650a88e71dfbb8/ADV190023/GPO/{1739C31B-397E-4E1C-B914-60BB91266283}/gpreport.xml -------------------------------------------------------------------------------- /ADV190023/README.md: -------------------------------------------------------------------------------- 1 | # Collection of Scripts for ADV190023 2 | 3 | Here the describes the scripts from [atiedemann](https://github.com/atiedemann) that make it easier for an administrator of an Active Directory domain to find out if their own domain is affected. 4 | 5 | The following is deposited for this purpose: 6 | 7 | 1. **PowerShell Script**: ActiveDirectory-DCLDAPEvents.ps1 8 | 2. **GPO**: Group policy for the configuration of the remote management of systems (domain controllers) 9 | 3. **PowerShell Script**: Start-LDAP_Interface_Event_Logging.ps1 10 | 11 | ## Requirements 12 | For best results, run the scripts on a system that meets these requirements. 13 | - Windows Server 2012 R2 or later 14 | - PowerShell module: Active Directory 15 | - PowerShell Remoting must be configured for all domain controllers! (GPO for this is in the GPO folder) 16 | - FireWall configuration for remote access must be configured! This is also configured by the above mentioned GPO. 17 | - Membership in the group Domain Admins 18 | 19 | # ActiveDirectory-DCLDAPEvents.ps1 20 | This script checks all domain controllers of the domain via Get-WinEvent and searches for the event with the ID 2887 in the Directory Service Event Log. If this script finds domain controllers where the event occurs, a CSV file is created in the directory ~\Documents and after checking all domain controllers a table is displayed in the shell. 21 | 22 | Header of the CSV File: 23 | "DomainController","Count" 24 | 25 | # No domain controller or some are not configured for remote management 26 | If you find that some domain controllers are not configured for remote management, you can use the provided Group Policy to configure remote management for all domain controllers. 27 | 28 | ## Which settings are distributed 29 | The GPO configures the following areas: 30 | - Enable the following services: 31 | - Windows Management Instrumentation 32 | - Windows Remote Management 33 | - Windows Firewall: The firewall is not activated, only rules are distributed that allow remote management. 34 | - WinRM Client and Service 35 | 36 | ### Setup Group Policy 37 | To configure Group Policy, create a new blank policy on the domain controller container and import the policy settings from the directory located in the GPO directory. When you have successfully completed the import and enabled the policy, wait two hours to enable the policies on all domain controllers. If you have multiple sites, policy activation may take longer. 38 | 39 | # ActiveDirectory-LDAPInterfaceEventLogging.ps1 40 | ## Usage 41 | To execute the script, start an **Administrative** PowerShell and change to the directory where you have saved the script. 42 | The script can be started without parameters! 43 | 44 | 45 | Run the script in Standard mode 46 | Here the logging for each domain controller is activated for 30 minutes! 47 | .\ActiveDirectory-LDAPInterfaceEventLogging.ps1 48 | 49 | Version with specification of the running time in minutes 50 | .\ActiveDirectory-LDAPInterfaceEventLogging.ps1 -Runtime "Minuten" 51 | 52 | ## Evaluation of simple and unsigned LDAP bindings of the domain controllers 53 | This script reads the previously created CSV file if it is not older than 15 minutes. If the file is older, the check is automatically repeated and the CSV file is rewritten. The CSV file is read and every domain controller that has at least one event (ID 2887 & 3040) is activated for LDAP logging. 54 | 55 | ### What tasks does the script perform? 56 | - Get-ADDomain queries the container of the domain controller and searches for computer objects in it 57 | - Each domain controller is checked for availability and for event 2887 and 3040 in the Directory Service Event Log 58 | - If the domain controller is reachable and the event was found, it is checked if it is reachable via remote management 59 | - If the domain controller is accessible: 60 | - The size of the **Directory Service** event log is read and written to a variable 61 | - The size of the event log is set to 3GB and logging is activated 62 | - Then the logging is carried out for the specified time 63 | - If the time has elapsed, the events are read from the event log and written to a CSV file 64 | - The original size of the event log is reset 65 | - The local CSV file is then copied to the executing server and renamed 66 | - This procedure is repeated for each domain controller in the CSV file! 67 | 68 | 69 | The following registry settings are changed:
70 | HKLM:"SYSTEM\CurrentControlSet\Services\EventLog\Directory Service" -Name 'MaxSize' -Type DWord -Value '3221225472'
71 | HKLM:"SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics" -Name '16 LDAP Interface Events'
72 | 73 | The CSV files are stored in the Documents folder of the logged-in user, where the header is created as follows: "DomainController","IPAddress","Port","User","BindType" 74 | 75 | %UserProfile%\Documents 76 | 77 | After all domain controllers have been queried, all local CSV files are read and the sum of all files is written to the file %UserProfile%\Documents\InsecureLDAPBinds.csv. The output in the PowerShell is grouped by domain controller and source IP. 78 | -------------------------------------------------------------------------------- /Active Directory/Microsoft-ADFilterUserToGroup.ps1: -------------------------------------------------------------------------------- 1 | $initialgroup = Get-ADGroupMember -Identity "GroupA" -Recursive | Select Name 2 | 3 | #Create yourself a "bad list" of people that you don't want in your new list. These are people in GroupB, GroupC, GroupD: 4 | 5 | $badlist = Get-ADGroupMember -Identity "GroupB" -Recursive | Select Name 6 | $badlist += Get-ADGroupMember -Identity "GroupC" -Recursive | Select Name 7 | $badlist += Get-ADGroupMember -Identity "GroupD" -Recursive| Select Name 8 | 9 | #Now you can build your desired list by comparing these two lists, and exclude the names that are in BOTH your GroupA and bad list. 10 | 11 | $final_list = @() 12 | ForEach($g in $initialgroup){ 13 | if($badlist.Name -notcontains $g.Name){ 14 | $final_list += $g 15 | } 16 | } 17 | 18 | $final_list | sort Name | Export-CSV -Path "C:\Temp\Users-To-Add.csv" 19 | 20 | 21 | Import-Csv -Path “C:\Temp\Users-To-Add.csv” | ForEach-Object {Add-ADGroupMember -Identity “Group-Name” -Members $_.’User-Name’} 22 | 23 | Add-ADGroupMember -Identity 'New Group' -Members (Get-ADGroupMember -Identity 'Old Group' -Recursive) 24 | -------------------------------------------------------------------------------- /Active Directory/Microsoft-CheckFixDomainMembership.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 3 2 | <# 3 | Check domain membership is ok and if not fix via stored credentials 4 | 5 | @guyrleech 16/12/19 6 | #> 7 | 8 | <# 9 | .SYNOPSIS 10 | 11 | Check domain membership is ok and if not fix via stored credentials 12 | 13 | .PARAMETER username 14 | 15 | Domain account with permissions to fix the secure channel 16 | 17 | .PARAMETER credential 18 | 19 | A credential object with permissions to fix the secure channel 20 | 21 | .PARAMETER password 22 | 23 | A secure string representation of the password for the account used to fix the secure channel 24 | 25 | .PARAMETER passwordFile 26 | 27 | Path to a file containing a secure string representation of the password for the account used to fix the secure channel 28 | 29 | .PARAMETER server 30 | 31 | Specific domain controller to target, otherwise one is picked 32 | 33 | .PARAMETER retries 34 | 35 | The number of times to retry repairing the secure channel 36 | 37 | .PARAMETER logfile 38 | 39 | Path to a file which will have a log from the script appended to it 40 | 41 | .PARAMETER sleepForMilliSeconds 42 | 43 | The number of milliseconds to sleep for between retries 44 | 45 | .PARAMETER encrypt 46 | 47 | Will prompt for a password which will be output such that it can be placed in a file for the -passwordFile option or passed directly to -password 48 | 49 | .PARAMETER simulateFail 50 | 51 | Simulates the secure channel test failing such that the fixing can be tested 52 | 53 | .EXAMPLE 54 | 55 | & '.\Microsoft-CheckFixDomainMembership.ps1' -encrypt 56 | 57 | Will prompt for a password such that it can be placed in a file for the -passwordFile option or passed directly to -password 58 | 59 | .EXAMPLE 60 | 61 | & '.\Microsoft-CheckFixDomainMembership.ps1' -username domain\someuser -passwordFile c:\scripts\crud -retries 4 62 | 63 | If the secure channel is broken, up to four attempts will be made to fix the secure channel using the domain\someuser account and the secure string password stored in c:\scripts\crud 64 | which has been generated previosuly via the -encrypt option 65 | 66 | .NOTES 67 | 68 | Does not require a reboot once it has fixed the secure channel 69 | 70 | Run at startup and/or periodically via a scheduled task, running under a local administrator account 71 | #> 72 | 73 | [CmdletBinding()] 74 | 75 | Param 76 | ( 77 | [string]$username , 78 | [Parameter(Mandatory=$true,ParameterSetName='Credential',HelpMessage='Credential object for domain join account')] 79 | [System.Management.Automation.PSCredential]$credential , 80 | [Parameter(Mandatory=$true,ParameterSetName='Password',HelpMessage='Secure string password for domain join account')] 81 | [string]$password , 82 | [Parameter(Mandatory=$true,ParameterSetName='PasswordFile',HelpMessage='File containing secure string password for domain join account')] 83 | [string]$passwordFile , 84 | [string]$server , 85 | [int]$retries = 5 , 86 | [string]$logFile , 87 | [int]$sleepForMilliSeconds = 1000 , 88 | [Parameter(Mandatory=$true,ParameterSetName='Encrypt',HelpMessage='Enter and encrypt a password')] 89 | [switch]$encrypt , 90 | [switch]$simulateFail 91 | ) 92 | 93 | If( $encrypt ) 94 | { 95 | $credential = Get-Credential -Message "Enter password for encryption" -UserName 'N\A' 96 | If( $credential ) 97 | { 98 | $credential.Password|ConvertFrom-SecureString 99 | Exit 0 100 | } 101 | Else 102 | { 103 | Exit 1 104 | } 105 | } 106 | 107 | If( $PSBoundParameters[ 'logfile' ] ) 108 | { 109 | Start-Transcript -Path $logfile -Append 110 | } 111 | 112 | Try 113 | { 114 | [hashtable]$parameters = @{ 'ErrorAction' = 'SilentlyContinue' } 115 | 116 | If( $PSBoundParameters[ 'server' ] ) 117 | { 118 | $parameters.Add( 'Server' , $server ) 119 | } 120 | 121 | If( $simulateFail -or ! ( Test-ComputerSecureChannel @parameters ) ) 122 | { 123 | Write-Verbose -Message 'Test-ComputerSecureChannel returned false' 124 | 125 | If( ! $PSBoundParameters[ 'credential' ] ) 126 | { 127 | If( ! $PSBoundParameters[ 'username' ] ) 128 | { 129 | $username = '{0}\{1}' -f $env:USERDOMAIN , $env:USERNAME 130 | } 131 | 132 | If( $PSBoundParameters[ 'passwordFile' ] ) 133 | { 134 | $password = Get-Content -Path $passwordFile 135 | } 136 | 137 | [System.Security.SecureString]$secureString = ConvertTo-SecureString -String $password -ErrorAction Stop 138 | $credential = New-Object -TypeName System.Management.Automation.PSCredential( $username , $secureString ) 139 | If( ! $credential ) 140 | { 141 | Throw "Failed to create credential for $username" 142 | } 143 | } 144 | 145 | $parameters += @{ 'Repair' = $true ; 'Credential' = $credential } 146 | 147 | [bool]$result = $false 148 | While( $retries -gt 0 -and ! $result ) 149 | { 150 | $result = Test-ComputerSecureChannel @parameters 151 | If( ! $result -and $sleepForMilliSeconds -gt 0 ) 152 | { 153 | Start-Sleep -Milliseconds $sleepForMilliSeconds 154 | } 155 | $retries-- 156 | } 157 | 158 | If( $result ) 159 | { 160 | Write-Output -InputObject 'Domain membership repaired ok' 161 | } 162 | Else 163 | { 164 | Write-Error -Message 'Failed to repair domain membership' 165 | } 166 | } 167 | } 168 | Catch 169 | { 170 | Throw $_ 171 | } 172 | Finally 173 | { 174 | If( $PSBoundParameters[ 'logfile' ] ) 175 | { 176 | Stop-Transcript 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /Active Directory/Microsoft-CleanADGroupMembers.ps1: -------------------------------------------------------------------------------- 1 | #requires -modules ActiveDirectory 2 | <# 3 | .SYNOPSIS 4 | Clean out disabled user accounts from a selected AD Group 5 | .DESCRIPTION 6 | This script provides a GUI to quickly clean an existing AD group from disabled user accounts, optionally recursive 7 | .PARAMETER 8 | None 9 | .INPUTS 10 | AD Domain, Source AD group, Recursive 11 | .OUTPUTS 12 | CSVFile with cleaned out users 13 | .NOTES 14 | Version: 1.0 15 | Author: Bart Jacobs - @Cloudsparkle 16 | Creation Date: 30/09/2020 17 | Purpose/Change: Clean AD group of disabled user accounts 18 | .EXAMPLE 19 | None 20 | #> 21 | 22 | #Custom function to get Recursive AD Group Members 23 | function Get-ADGroupMemberRecursive 24 | { 25 | [CmdletBinding()] 26 | param 27 | ( 28 | [parameter(Mandatory = $true )] 29 | [string]$Identity, 30 | [string]$Server 31 | ) 32 | 33 | $RecMembers = Get-ADGroupMember -Identity $Identity -Server $Server 34 | 35 | foreach($RecMember in $RecMembers) 36 | { 37 | $RecMember 38 | 39 | if( $RecMember.objectClass -eq 'group' ) 40 | { 41 | Get-ADGroupMemberRecursive -Identity $RecMember.SID -Server $Server 42 | } 43 | } 44 | } 45 | 46 | #Making sure we can have those fancy messageboxes working... 47 | Add-Type -AssemblyName PresentationFramework 48 | 49 | #Check for running as administrator, if not: exit 50 | Write-Host "Checking for elevated permissions..." 51 | if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) 52 | { 53 | [System.Windows.MessageBox]::Show("Insufficient permissions to run this script. Open the PowerShell console as an administrator and run this script again.","Error","OK","Error") 54 | exit 1 55 | } 56 | else 57 | { 58 | Write-Host "Code is running as administrator — go on executing the script..." -ForegroundColor Green 59 | } 60 | 61 | #Initialize variables 62 | $SelectedDomain = "" 63 | $ADGroup = "" 64 | $Subgroupname = "" 65 | $ToCleanGroup = "" 66 | $CSVFile = "c:\temp\DisabledAccountsCleaned.csv" 67 | $csvContents = @() # Create the empty array that will eventually be the CSV file 68 | 69 | #Get the AD DomainName 70 | $ADForestInfo = Get-ADForest 71 | $SelectedDomain = $ADForestInfo.Domains | Out-GridView -Title "Select AD Domain" -OutputMode Single 72 | 73 | #Check for a valid DomainName 74 | if ($SelectedDomain -eq $null) 75 | { 76 | [System.Windows.MessageBox]::Show("AD Domain not selected","Error","OK","Error") 77 | exit 1 78 | } 79 | 80 | #Find the right AD Domain Controller 81 | $dc = Get-ADDomainController -DomainName $SelectedDomain -Discover -NextClosestSite 82 | 83 | #Get all groups and select the group to clean 84 | $ADGroupList = Get-ADGroup -filter * -Server $SelectedDomain | sort name | select Name 85 | $ADGroup = $ADGroupList | Out-GridView -Title "Select the AD Group that needs cleaning" -OutputMode Single 86 | 87 | #Basic checks for selecte groups 88 | if ($ADGroup -eq $null) 89 | { 90 | [System.Windows.MessageBox]::Show("Source group not selected","Error","OK","Error") 91 | exit 1 92 | } 93 | 94 | #Ask for 95 | $DoRecursive = [System.Windows.MessageBox]::Show('Do you want to clean subgroups too?','Run recursive?','YesNo','Question') 96 | 97 | #Fetch all members from selecte source group 98 | if ($DoRecursive -eq "Yes") 99 | { 100 | #write-host "Recursive Mode" 101 | $members = Get-ADGroupMemberRecursive -Identity $ADGroup.Name -Server $dc.HostName[0] 102 | } 103 | Else 104 | { 105 | #write-host "Non-Recursive Mode" 106 | $members = Get-ADGroupMember -Identity $ADGroup.Name -Server $dc.HostName[0] 107 | } 108 | 109 | #Try to clean the selected AD Group 110 | Try 111 | { 112 | foreach ($member in $members) 113 | { 114 | If ($member.objectClass -eq "group") 115 | { 116 | $Subgroupname = $member.SamAccountName 117 | } 118 | Else 119 | { 120 | $ADUSer = get-aduser -Identity $member 121 | if ($ADUSer.Enabled -eq $False) 122 | { 123 | if ($Subgroupname -eq "") 124 | { 125 | $ToCleanGroup = $ADGroup.Name 126 | } 127 | Else 128 | { 129 | $ToCleanGroup = $Subgroupname 130 | } 131 | 132 | #uncomment below for rolling output 133 | #write-host $member.SamAccountName, $ToCleanGroup 134 | Remove-ADGroupMember -Identity $ToCleanGroup -Members $member.samaccountname -Confirm:$False 135 | 136 | #Get the CSV data ready 137 | $row = New-Object System.Object # Create an object to append to the array 138 | $row | Add-Member -MemberType NoteProperty -Name "Account" -Value $member.SamAccountName 139 | $row | Add-Member -MemberType NoteProperty -Name "GroupName" -Value $ToCleanGroup 140 | 141 | $csvContents += $row # append the new data to the array# 142 | } 143 | } 144 | } 145 | 146 | #Write the CSV output 147 | $csvContents | Export-CSV -path $CSVFile -NoTypeInformation 148 | 149 | $message = "The AD Group " + $ADGroup.name + " has been cleaned." 150 | [System.Windows.MessageBox]::Show($message,"Finished","OK","Asterisk") 151 | 152 | } 153 | Catch 154 | { 155 | [System.Windows.MessageBox]::Show("AD Group cleaning failed","Error","OK","Error") 156 | exit 1 157 | } -------------------------------------------------------------------------------- /Active Directory/Microsoft-CopyADGroupMembers.ps1: -------------------------------------------------------------------------------- 1 | #requires -modules ActiveDirectory 2 | <# 3 | .SYNOPSIS 4 | Copy AD group members from one AD group to another AD group in the same domain 5 | .DESCRIPTION 6 | This script provides a GUI to quickly copy AD group members to another existing group in the same domain. Multi-domain forests are supported, the script will query for the AD domain. 7 | .PARAMETER 8 | None 9 | .INPUTS 10 | AD Domain, Source AD group, Destination AD Group 11 | .OUTPUTS 12 | None 13 | .NOTES 14 | Version: 1.1 15 | Author: Bart Jacobs - @Cloudsparkle 16 | Creation Date: 09/03/2020 17 | Purpose/Change: Copy AD Group members to another group 18 | 19 | .EXAMPLE 20 | None 21 | #> 22 | 23 | #Initialize variables 24 | $SelectedDomain = "" 25 | $SourceGroup = "" 26 | $DestinationGroup = "" 27 | 28 | Add-Type -AssemblyName PresentationFramework 29 | 30 | #Get the AD DomainName 31 | $ADForestInfo = Get-ADForest 32 | $SelectedDomain = $ADForestInfo.Domains | Out-GridView -Title "Select AD Domain" -OutputMode Single 33 | 34 | #Check for a valid DomainName 35 | if ($SelectedDomain -eq $null) 36 | { 37 | [System.Windows.MessageBox]::Show("AD Domain not selected","Error","OK","Error") 38 | exit 39 | } 40 | 41 | #Find the right AD Domain Controller 42 | $dc = Get-ADDomainController -DomainName $SelectedDomain -Discover -NextClosestSite 43 | 44 | #Get all groups from selected and select source and destination groups 45 | $ADGroupList = Get-ADGroup -filter * -Server $SelectedDomain | sort name | select Name 46 | $SourceGroup = $ADGroupList | Out-GridView -Title "Select the AD Group Name who's members needs to be copied" -OutputMode Single 47 | $DestinationGroup = $ADGroupList | Out-GridView -Title "Select the AD Group Name that needs to be populated" -OutputMode Single 48 | 49 | #Basic checks for selecte groups 50 | if ($SourceGroup -eq $null) 51 | { 52 | [System.Windows.MessageBox]::Show("Source group not selected","Error","OK","Error") 53 | exit 1 54 | } 55 | 56 | if ($DestinationGroup -eq $null) 57 | { 58 | [System.Windows.MessageBox]::Show("Destination group not selected","Error","OK","Error") 59 | exit 1 60 | } 61 | 62 | if ($SourceGroup -eq $DestinationGroup) 63 | { 64 | [System.Windows.MessageBox]::Show("Source and Destination groups can not be the same","Error","OK","Error") 65 | exit 1 66 | } 67 | 68 | #Fetch all members from selecte source group 69 | $member = Get-ADGroupMember -Identity $SourceGroup.Name -Server $dc.HostName[0] 70 | 71 | #Try to populate the selected destination group with members 72 | Try 73 | { 74 | Add-ADGroupMember -Identity $DestinationGroup.name -Members $member -Server $dc.HostName[0] 75 | $message = "Members of AD Group " + $SourceGroup.name + " have been copied to AD Group " + $DestinationGroup.Name 76 | [System.Windows.MessageBox]::Show($message,"Finished","OK","Asterisk") 77 | } 78 | Catch 79 | { 80 | [System.Windows.MessageBox]::Show("AD Group membership copy failed","Error","OK","Error") 81 | } -------------------------------------------------------------------------------- /Active Directory/README.md: -------------------------------------------------------------------------------- 1 | # Microsoft Active Directory 2 | 3 | ## Microsoft-ADAccountExpiry.ps1 4 | Find AD accounts with passwords or accounts expiring within the specified number of days or are locked out or disabled and optionally send an email containing the information 5 | -------------------------------------------------------------------------------- /FSLogix/FSLogix-LocalProfileToProfileContainer.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script migrate from Local Profile to FSLogix Profile Container 4 | 5 | .DESCRIPTION 6 | Test before using!! 7 | 8 | .NOTES 9 | Version: 1.0 10 | Author: 11 | Rewrite Author: Manuel Winkel 12 | Creation Date: 2020-03-04 13 | Purpose/Change: 14 | NOTE: This only works between like profile versions. eg. You can’t migrate your 2008R2 profiles to Server 2016 and expect it to work. 15 | This requires using frx.exe, which means that FSLogix needs to be installed on the server that contains the profiles. The script will create the folders in the USERNAME_SID format, and set all proper permissions. 16 | Use this script. Edit it. Run it (as administrator) from the Citrix server. It will pop up this screen to select what profiles to migrate. 17 | #> 18 | 19 | ######################################################################################### 20 | # Setup Parameter first here newprofilepath 21 | # Requires -RunAsAdministrator 22 | # Requires FSLogix Agent (frx.exe) 23 | ######################################################################################### 24 | # Example "\\domain.com\share\path" 25 | # fslogix Root profile path 26 | $newprofilepath = "\\domain.com\share\path" 27 | 28 | ######################################################################################### 29 | # Do not edit here 30 | ######################################################################################### 31 | $ENV:PATH=”$ENV:PATH;C:\Program Files\fslogix\apps\” 32 | $oldprofiles = gci c:\users | ?{$_.psiscontainer -eq $true} | select -Expand fullname | sort | out-gridview -OutputMode Multiple -title "Select profile(s) to convert" 33 | 34 | # foreach old profile 35 | foreach ($old in $oldprofiles) { 36 | 37 | $sam = ($old | split-path -leaf) 38 | $sid = (New-Object System.Security.Principal.NTAccount($sam)).translate([System.Security.Principal.SecurityIdentifier]).Value 39 | 40 | # set the nfolder path to \\newprofilepath\username_sid 41 | $nfolder = join-path $newprofilepath ($sam+"_"+$sid) 42 | # if $nfolder doesn't exist - create it with permissions 43 | if (!(test-path $nfolder)) {New-Item -Path $nfolder -ItemType directory | Out-Null} 44 | & icacls $nfolder /setowner "$env:userdomain\$sam" /T /C 45 | & icacls $nfolder /grant $env:userdomain\$sam`:`(OI`)`(CI`)F /T 46 | 47 | # sets vhd to \\nfolderpath\profile_username.vhdx (you can make vhd or vhdx here) 48 | $vhd = Join-Path $nfolder ("Profile_"+$sam+".vhdx") 49 | 50 | frx.exe copy-profile -filename $vhd -sid $sid 51 | } 52 | -------------------------------------------------------------------------------- /FSLogix/FSLogix-MigrateFromSIDToUsernameFolder.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 3 2 | <# 3 | .SYNOPSIS 4 | Migrate FSLogix Container vom Folder Name Schema SID_USERNAME to USERNAME_SID 5 | .DESCRIPTION 6 | If you don't set the more readable naming schema (USERNAME_SID) directly, you can't just do this afterwards. 7 | Because it creates new folders and there, if not migrated before, new containers. 8 | Therefore copy the existing containers to the new location before. 9 | .NOTES 10 | Version: 1.3 11 | Author: Manuel Winkel 12 | Creation Date: 2020-12-09 13 | Purpose/Change: 14 | <# 15 | 16 | 17 | .PARAMETER path 18 | 19 | Path to the old FSLogix Container Location 20 | 21 | .PARAMETER target 22 | 23 | Path to the new FSLogix Container Location. If not set, the source (path) is used as target. 24 | 25 | 26 | .PARAMETER delete 27 | 28 | Delete the source Disk Folder. 29 | 30 | 31 | .EXAMPLE 32 | 33 | & '.\FSLogix-MigrateFromSIDToUsernameFolder.ps1 -path D:\CTXFslogix -target D:\FSLogixCTX 34 | 35 | Copy the disks in the specified locations (New Naming Schema) and in all child items from Path D:\CTXFSLogix to D:\FSLogixCTX 36 | 37 | #> 38 | 39 | [CmdletBinding()] 40 | 41 | 42 | Param ( 43 | 44 | [Parameter( 45 | Mandatory = $true, 46 | HelpMessage = 'FSLogix Container Source Path' 47 | )] 48 | [System.String]$path, 49 | 50 | [Parameter( 51 | HelpMessage = 'FSLogix Container Target Path' 52 | )] 53 | [System.String]$target, 54 | 55 | [Parameter( 56 | HelpMessage = 'Delete Original Folder' 57 | )] 58 | [switch]$delete 59 | 60 | ) 61 | 62 | 63 | #Test for paths Error and exit if they are not present 64 | If (!Test-Path $path){ 65 | Write-Error "$path does not exist" 66 | break 67 | } 68 | If (!Test-Path $target) { 69 | Write-Error "$target does not exist" 70 | break 71 | } 72 | 73 | #Get all paths which end in vhdx and process them 74 | Get-ChildItem -Recurse $path2 -Include *vhdx -ErrorAction SilentlyContinue | ForEach-Object { 75 | 76 | $vhdPath = $_ 77 | $pathSource = $vhdPath.directory 78 | #Grab user name and sid using regex and swap them round warn and quit if no match 79 | if ((Split-Path -Path $pathSource -Leaf) -match "(.*?)_(.*)") { 80 | $newDirName = $matches[2] + '_' + $matches[1] 81 | } 82 | else { 83 | write-warning "$pathnew was not correct format" 84 | break 85 | } 86 | 87 | #create final path string 88 | $destnew = Join-Path $target $newDirName 89 | 90 | if (!(Test-Path -Path $destnew)) { 91 | New-Item -ItemType directory -Path $destnew 92 | } 93 | #move the disk 94 | Move-Item -Path $vhdPath -Destination $destnew 95 | #set permissions 96 | Get-Acl -Path $pathSource | Set-Acl -Path $destnew 97 | 98 | } 99 | if ($Delete) { 100 | Remove-Item $path -Recurse 101 | } -------------------------------------------------------------------------------- /FSLogix/FSLogix-MigrateFromUsernameToSIDFolder.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 3 2 | <# 3 | .SYNOPSIS 4 | Migrate FSLogix Container vom Folder Name Schema USERNAME_SID to SID_USERNAME 5 | .DESCRIPTION 6 | If you don't set the default naming schema (SID_USERNAME) directly, you can't just do this afterwards. 7 | Because it creates new folders and there, if not migrated before, new containers. 8 | Therefore copy the existing containers to the new location before. 9 | .NOTES 10 | Version: 1.0 11 | Author: Manuel Winkel 12 | Creation Date: 2024-05-15 13 | Purpose/Change: 14 | <# 15 | 16 | 17 | .PARAMETER path 18 | 19 | Path to the old FSLogix Container Location 20 | 21 | .PARAMETER target 22 | 23 | Path to the new FSLogix Container Location. If not set, the source (path) is used as target. 24 | 25 | .PARAMETER delete 26 | 27 | Delete the source Disk Folder. 28 | 29 | .PARAMETER copy 30 | 31 | Copy the source Disk Folder. 32 | 33 | .EXAMPLE 34 | 35 | & '.\FSLogix-MigrateFromUsernameToSIDFolder.ps1 -path D:\CTXFslogix -target D:\FSLogixCTX 36 | 37 | Copy the disks in the specified locations (New Naming Schema) and in all child items from Path D:\CTXFSLogix to D:\FSLogixCTX 38 | 39 | #> 40 | 41 | [CmdletBinding()] 42 | 43 | 44 | Param ( 45 | 46 | [Parameter( 47 | Mandatory = $true, 48 | HelpMessage = 'FSLogix Container Source Path' 49 | )] 50 | [System.String]$path, 51 | 52 | [Parameter( 53 | HelpMessage = 'FSLogix Container Target Path' 54 | )] 55 | [System.String]$target, 56 | 57 | [Parameter( 58 | HelpMessage = 'Delete Original Folder' 59 | )] 60 | [switch]$delete, 61 | 62 | [Parameter( 63 | HelpMessage = 'Copy not move Original Folder' 64 | )] 65 | [switch]$copy 66 | ) 67 | 68 | 69 | #Test for paths Error and exit if they are not present 70 | If (!(Test-Path $path)){ 71 | Write-Error "$path does not exist" 72 | break 73 | } 74 | If (!(Test-Path $target)) { 75 | Write-Error "$target does not exist" 76 | break 77 | } 78 | 79 | #Get all paths which end in vhdx and process them 80 | Get-ChildItem -Recurse $path -Include *vhdx -ErrorAction SilentlyContinue | ForEach-Object { 81 | 82 | $vhdPath = $_ 83 | $pathSource = $vhdPath.directory 84 | #Grab user name and sid using regex and swap them round warn and quit if no match 85 | if ((Split-Path -Path $pathSource -Leaf) -match "(.*?)_(.*)") { 86 | $newDirName = $matches[2] + '_' + $matches[1] 87 | } 88 | else { 89 | write-warning "$pathnew was not correct format" 90 | break 91 | } 92 | 93 | #create final path string 94 | $destnew = Join-Path $target $newDirName 95 | 96 | if (!(Test-Path -Path $destnew)) { 97 | New-Item -ItemType directory -Path $destnew 98 | } 99 | #move the disk 100 | if ($Copy) { 101 | Copy-Item -Path $vhdPath -Destination $destnew 102 | } else { 103 | Move-Item -Path $vhdPath -Destination $destnew 104 | } 105 | #set permissions 106 | Get-Acl -Path $pathSource | Set-Acl -Path $destnew 107 | 108 | } 109 | if ($Delete) { 110 | Remove-Item $path -Recurse 111 | } 112 | -------------------------------------------------------------------------------- /FSLogix/FSLogix-MoveContainerOfNotExistingUser.ps1: -------------------------------------------------------------------------------- 1 | import-module ActiveDirectory 2 | 3 | $ODFCShareSource = "\\MyServer\SourceShare" 4 | $ODFCShareDestination = "\\MyServer\DestinationShare" 5 | $UnusedDirMoveLogPath = "C:\UnusedHomeDirMove_log.txt" 6 | $OutPutErrorLogPath = "C:\UnusedHomeDirMove_OutputErrors_log.txt" 7 | 8 | $folders = Get-ChildItem $ODFCShareSource 9 | 10 | ForEach ($folder in $folders) 11 | { 12 | $User = Get-ADUser -LDAPFilter "(sAMAccountName=$($folder.Name.split('_')[1]))" 13 | If ($User -eq $Null) 14 | { 15 | $ODFCDir = $ODFCShareSource+"\"+$folder 16 | $ODFCShareDestinationDir = $ODFCShareDestination+"\"+$folder 17 | $ErrorActionPreference = "Stop" 18 | Try 19 | { 20 | Copy-Item -Recurse $ODFCDir $ODFCShareDestinationDir -Force 21 | } 22 | Catch 23 | { 24 | "--- ERROR" | Out-File $OutPutErrorLogPath -Append 25 | "$folder" | Out-File $OutPutErrorLogPath -Append 26 | $_ | Out-File $OutPutErrorLogPath -Append 27 | "---------" | Out-File $OutPutErrorLogPath -Append 28 | } 29 | if ((Test-Path -path $ODFCShareDestinationDir) -eq $True) 30 | { 31 | $ErrorActionPreference = "Stop" 32 | Try 33 | { 34 | cmd.exe /c RD /S /Q $ODFCDir 35 | if ((Test-Path -path $HomeDir) -eq $True) 36 | { 37 | Start-Sleep -s 10 38 | cmd.exe /c RD /S /Q $ODFCDir 39 | } 40 | } 41 | Catch 42 | { 43 | "--- ERROR" | Out-File $OutPutErrorLogPath -Append 44 | "$folder" | Out-File $OutPutErrorLogPath -Append 45 | $_ | Out-File $OutPutErrorLogPath -Append 46 | "---------" | Out-File $OutPutErrorLogPath -Append 47 | } 48 | } 49 | if ((Test-Path -path $ODFCDir) -eq $False) 50 | { 51 | if ((Test-Path -path $ODFCShareDestinationDir) -eq $False) 52 | { 53 | $LogEntryDate = Get-Date –Format G 54 | Add-Content -Path $UnusedDirMoveLogPath -Value "--- ERROR" 55 | Add-Content -Path $UnusedDirMoveLogPath -Value "$LogEntryDate" 56 | Add-Content -Path $UnusedDirMoveLogPath -Value "$folder" 57 | Add-Content -Path $UnusedDirMoveLogPath -Value "Folder did not move correctly" 58 | } 59 | Else 60 | { 61 | $LogEntryDate = Get-Date –Format G 62 | Add-Content -Path $UnusedDirMoveLogPath -Value "--- OK" 63 | Add-Content -Path $UnusedDirMoveLogPath -Value "$LogEntryDate" 64 | Add-Content -Path $UnusedDirMoveLogPath -Value "$folder" 65 | Add-Content -Path $UnusedDirMoveLogPath -Value "Folder was moved correctly" 66 | } 67 | } 68 | Else 69 | { 70 | $LogEntryDate = Get-Date –Format G 71 | Add-Content -Path $UnusedDirMoveLogPath -Value "--- ERROR" 72 | Add-Content -Path $UnusedDirMoveLogPath -Value "$LogEntryDate" 73 | Add-Content -Path $UnusedDirMoveLogPath -Value "$folder" 74 | Add-Content -Path $UnusedDirMoveLogPath -Value "Folder did not move correctly" 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /FSLogix/FSLogix-MoveContainerToGPT.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script migrate from FSLogix Profile Container to FSLogix Profile Container 4 | 5 | .DESCRIPTION 6 | Test before using!! 7 | 8 | .NOTES 9 | Version: 1.0 10 | Author: 11 | Rewrite Author: Manuel Winkel 12 | Creation Date: 2023-08-21 13 | Purpose/Change: 14 | NOTE: This only works between like profile versions. eg. You can’t migrate your 2008R2 profiles to Server 2016 and expect it to work. 15 | This requires using frx.exe, which means that FSLogix needs to be installed on the server that contains the profiles. The script will create the folders in the USERNAME_SID format, and set all proper permissions. 16 | Use this script. Edit it. Run it (as administrator) from the Citrix server. It will pop up this screen to select what profiles to migrate. 17 | #> 18 | 19 | ######################################################################################### 20 | # Setup Parameter first here newprofilepath 21 | # Requires -RunAsAdministrator 22 | # Requires FSLogix Agent (frx.exe) 23 | ######################################################################################### 24 | # Example "\\domain.com\share\path" 25 | # fslogix Root profile path 26 | $newprofilepath = "\\domain.com\share\path" 27 | $oldprofilepath = "\\domain.com\share\path" 28 | ######################################################################################### 29 | # Do not edit here 30 | ######################################################################################### 31 | $ENV:PATH=”$ENV:PATH;C:\Program Files\fslogix\apps\” 32 | $oldprofiles = gci $oldprofilepath | ?{$_.psiscontainer -eq $true} | select -Expand fullname | sort | out-gridview -OutputMode Multiple -title "Select profile(s) to convert" 33 | 34 | # foreach old profile 35 | foreach ($old in $oldprofiles) { 36 | 37 | $sam = ($old | split-path -leaf) 38 | $sid = (New-Object System.Security.Principal.NTAccount($sam)).translate([System.Security.Principal.SecurityIdentifier]).Value 39 | 40 | # set the nfolder path to \\newprofilepath\username_sid 41 | $nfolder = join-path $newprofilepath ($sam+"_"+$sid) 42 | # if $nfolder doesn't exist - create it with permissions 43 | if (!(test-path $nfolder)) {New-Item -Path $nfolder -ItemType directory | Out-Null} 44 | & icacls $nfolder /setowner "$env:userdomain\$sam" /T /C 45 | & icacls $nfolder /grant $env:userdomain\$sam`:`(OI`)`(CI`)F /T 46 | 47 | # sets vhd to \\nfolderpath\profile_username.vhdx (you can make vhd or vhdx here) 48 | $vhd = Join-Path $nfolder ("Profile_"+$sam+".vhdx") 49 | $oldvhd = Join-Path $old ("Profile_"+$sam+".vhdx") 50 | 51 | $script1 = "create vdisk file=`"$vhd`" maximum 30720 type=expandable" 52 | $script2 = "sel vdisk file=`"$vhd`"`r`nattach vdisk" 53 | $script3 = "sel vdisk file=`"$vhd`"`r`ncreate part prim`r`nselect part 2`r`nformat fs=ntfs quick" 54 | $script4 = "sel vdisk file=`"$vhd`"`r`nsel part 2`r`nassign letter=Y" 55 | $script5 = "sel vdisk file`"$vhd`"`r`ndetach vdisk" 56 | $script6 = "sel vdisk file=`"$oldvhd`"`r`nattach vdisk" 57 | $script7 = "sel vdisk file=`"$oldvhd`"`r`nsel part 1`r`nassign letter=z" 58 | $script8 = "sel vdisk file`"$oldvhd`"`r`ndetach vdisk" 59 | $script9 = "sel vdisk file=`"$vhd`"`r`nconvert GPT" 60 | 61 | if (!(test-path $vhd)) { 62 | $script1 | diskpart 63 | start-process icacls "$vhd /setowner $sam" 64 | $script2 | diskpart 65 | $script9 | diskpart 66 | Start-Sleep -s 5 67 | $script3 | diskpart 68 | $script4 | diskpart 69 | & label Y: Profile-$sam 70 | New-Item -Path Y:\Profile -ItemType directory | Out-Null 71 | Start-Sleep -s 5 72 | start-process icacls "Y:\Profile /setowner SYSTEM" 73 | Start-Process icacls -ArgumentList "Y:\Profile /inheritance:r" 74 | $cmd1 = "Y:\Profile /grant $env:userdomain\$sam`:`(OI`)`(CI`)F" 75 | Start-Process icacls -ArgumentList "Y:\Profile /grant SYSTEM`:`(OI`)`(CI`)F" 76 | Start-Process icacls -ArgumentList "Y:\Profile /grant Administrators`:`(OI`)`(CI`)F" 77 | Start-Process icacls -ArgumentList $cmd1 78 | Start-Sleep -s 5 79 | } else { 80 | 81 | $script2 | diskpart 82 | Start-Sleep -s 5 83 | $script4 | diskpart 84 | } 85 | "Copying $old to $vhd" 86 | $script6 | diskpart 87 | $script7 | diskpart 88 | Start-Sleep -s 5 89 | & robocopy Z:\Profile Y:\Profile /MIR /B /R:1 /W:1 /COPYALL | Out-Null 90 | Start-Sleep -s 5 91 | $script5 | diskpart 92 | $script8 | diskpart 93 | } 94 | -------------------------------------------------------------------------------- /FSLogix/FSLogix-OfficeContainerSizeWarning.ps1: -------------------------------------------------------------------------------- 1 | # **************************************************** 2 | # D. Mohrmann, S&L Firmengruppe, Twitter: @mohrpheus78 3 | # **************************************************** 4 | 5 | <# 6 | .SYNOPSIS 7 | Shows a message to user in the notificarion area if FSLogix Office container is almost full. 8 | 9 | .DESCRIPTION 10 | Gets information about the users FSLogix office container (size and remaining size) and calculates the free space in percent. 11 | 12 | .EXAMPLE 13 | .FSLogix Office container Size Warning.ps1 14 | 15 | .NOTES 16 | This script must be run on a machine where the user is currently logged on. 17 | Should be run as a powershell login script via GPO. 18 | Edit value $PercentFree -le 10 in line 34 to define the free percent. 19 | #> 20 | 21 | # Wait 10 sec. till showing the message 22 | Start-Sleep 10 23 | 24 | # Get the relevant informations from the FSLogix profile 25 | $FSLOContainerSize = Get-Volume -FileSystemLabel *Profile-$ENV:USERNAME* | Where-Object { $_.DriveType -eq 'Fixed'} 26 | 27 | # Execute only if FSLogix profile is available 28 | IF (!($FSLProfileSize -eq $nul)) 29 | { 30 | # Calculate the free space in percent 31 | $PercentFree = [Math]::round((($FSLOContainerSize.SizeRemaining/$FSLOContainerSize.size) * 100)) 32 | 33 | # If free space is less then 10 % show message 34 | IF ($PercentFree -le 10) {wlrmdr -s 25 -f 2 -t FSLogix Profile -m Attention! Your Office container contingent is almost exhausted. Please inform the IT service!} 35 | } 36 | -------------------------------------------------------------------------------- /FSLogix/FSLogix-ProfileContainerSizeWarning.ps1: -------------------------------------------------------------------------------- 1 | # **************************************************** 2 | # D. Mohrmann, S&L Firmengruppe, Twitter: @mohrpheus78 3 | # **************************************************** 4 | 5 | <# 6 | .SYNOPSIS 7 | Shows a message to user in the notificarion area if FSLogix profile is almost full. 8 | 9 | .DESCRIPTION 10 | Gets information about the users FSLogix profile (size and remaining size) and calculates the free space in percent. 11 | 12 | .EXAMPLE 13 | .FSLogix Profile Size Warning.ps1 14 | 15 | .NOTES 16 | This script must be run on a machine where the user is currently logged on. 17 | Should be run as a powershell login script via GPO. 18 | Edit value $PercentFree -le 10 in line 34 to define the free percent. 19 | #> 20 | 21 | # Wait 10 sec. till showing the message 22 | Start-Sleep 10 23 | 24 | # Get the relevant informations from the FSLogix profile 25 | $FSLProfileSize = Get-Volume -FileSystemLabel *Profile-$ENV:USERNAME* | Where-Object { $_.DriveType -eq 'Fixed'} 26 | 27 | # Execute only if FSLogix profile is available 28 | IF (!($FSLProfileSize -eq $nul)) 29 | { 30 | # Calculate the free space in percent 31 | $PercentFree = [Math]::round((($FSLProfileSize.SizeRemaining/$FSLProfileSize.size) * 100)) 32 | 33 | # If free space is less then 10 % show message 34 | IF ($PercentFree -le 10) {wlrmdr -s 25 -f 2 -t FSLogix Profile -m Warning! Your profile contingent is almost exhausted. Please inform the IT service!} 35 | } 36 | -------------------------------------------------------------------------------- /FSLogix/FSLogix-ProfileDiskToProfileContainer.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script migrate from Profile Disk to FSLogix Profile Container 4 | 5 | .DESCRIPTION 6 | Test before using!! 7 | 8 | .NOTES 9 | Version: 1.0 10 | Author: 11 | Rewrite Author: Manuel Winkel 12 | Creation Date: 2020-03-04 13 | Purpose/Change: 14 | #> 15 | ######################################################################################### 16 | # Setup Parameter first here UPDPath NewProfilePath DiskProfileFolder UserListe Users 17 | # Requires -RunAsAdministrator 18 | # My Userprofiles come only with SAMAccount Name without Domain "\Username\2012R2\UPM_Profile 19 | ######################################################################################### 20 | # Example from my UPM Path "\\path_to_your_share\upd$\Profile" 21 | # fslogix Root profile path 22 | $NewProfilePath = '\\path_to_your_share\fslogix$' 23 | # Profile Disk Root profile path 24 | $UPDPath = '\\path_to_your_share\upd$\' 25 | # Disk Profile Folder - First Path to Disk Profile Folder - see my example above 26 | $DiskProfileFolder = 'Profile' 27 | #Define the path to the user list and read it 28 | $UserListe = 'C:\temp\UserMigrate.txt' 29 | $Users = Get-Content $UserListe 30 | 31 | ######################################################################################### 32 | # Do not edit here 33 | ######################################################################################### 34 | 35 | foreach ($U in $Users){ 36 | # User from the file corresponds to SAM 37 | $SAM = $U 38 | # Read SID based on SAM 39 | $SID = (New-Object System.Security.Principal.NTAccount($SAM)).translate([System.Security.Principal.SecurityIdentifier]).Value 40 | # Defining the path to the original UPD 41 | $UPD = Join-Path -Path $UPDPath -ChildPath ('UVHD-' + $SID + '.vhdx') 42 | Write-Output "Start with User: $SAM" 43 | If (Test-Path $UPD){ 44 | # If UPD file exists, define target path 45 | $FSLPath = Join-Path -Path $NewProfilePath -ChildPath ($SAM + '_' + $SID) 46 | # Create the destination folder 47 | If (!(Test-Path $FSLPath)){ 48 | Write-Output "Create Folder: $FSLPath" 49 | New-Item -Path $NewProfilePath -Name ($SAM + '_' + $SID) -ItemType Directory | Out-Null 50 | } 51 | # Set permissions from destination folder 52 | & icacls $FSLPath /setowner "$env:userdomain\$sam" /T /C | Out-Null 53 | & icacls $FSLPath /grant $env:userdomain\$sam`:`(OI`)`(CI`)F /T | Out-Null 54 | 55 | # Define destination file path 56 | $FSLDisk = Join-Path -Path $FSLPath -ChildPath ('Profile_' + $SAM + '.vhdx') 57 | # Copy profile disk to new destination 58 | Write-Output "Copy UPD: $UPD" 59 | Copy-Item -Path $UPD -Destination $FSLDisk | Out-Null 60 | # Mound Disk Image 61 | Mount-DiskImage -ImagePath $FSLDisk 62 | # Get drive letter 63 | $DriveLetter = (Get-DiskImage -ImagePath $FSLDisk | Get-Disk | Get-Partition).DriveLetter 64 | $MountPoint = ($DriveLetter + ':\') 65 | 66 | # Define path in the profile disk 67 | $DiskProfilePath = Join-Path -Path $MountPoint -ChildPath $DiskProfileFolder 68 | # Create path in the profile disk 69 | If (!(Test-Path $DiskProfilePath)){ 70 | Write-Output "Create Folder: $DiskProfilePath" 71 | New-Item $DiskProfilePath -ItemType Directory| Out-Null 72 | } 73 | 74 | # Defining the files and folders that should not be copied 75 | $Excludes = @("Profile","Uvhd-Binding","`$RECYCLE.BIN","System Volume Information") 76 | 77 | # Copy profile disk content to the new profile folder 78 | $Content = Get-ChildItem $MountPoint -Force 79 | ForEach ($C in $Content){ 80 | 81 | If ($Excludes -notcontains $C.Name){ 82 | Write-Output ('Move: ' + $C.FullName) 83 | 84 | Try { 85 | Move-Item $C.FullName -Destination $DiskProfilePath -Force -ErrorAction Stop 86 | } Catch { 87 | Write-Warning "Error: $_" 88 | } 89 | } 90 | 91 | } 92 | 93 | # Defining the registry file 94 | $regtext = "Windows Registry Editor Version 5.00 95 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$SID] 96 | `"ProfileImagePath`"=`"C:\\Users\\$SAM`" 97 | `"Flags`"=dword:00000000 98 | `"State`"=dword:00000000 99 | `"ProfileLoadTimeLow`"=dword:00000000 100 | `"ProfileLoadTimeHigh`"=dword:00000000 101 | `"RefCount`"=dword:00000000 102 | `"RunLogonScriptSync`"=dword:00000001 103 | " 104 | 105 | # Create the folder and registry file 106 | Write-Output "Create Reg: $DiskProfilePath\AppData\Local\FSLogix\ProfileData.reg" 107 | if (!(Test-Path "$DiskProfilePath\AppData\Local\FSLogix")) { 108 | New-Item -Path "$DiskProfilePath\AppData\Local\FSLogix" -ItemType directory | Out-Null 109 | } 110 | if (!(Test-Path "$DiskProfilePath\AppData\Local\FSLogix\ProfileData.reg")) { 111 | $regtext | Out-File "$DiskProfilePath\AppData\Local\FSLogix\ProfileData.reg" -Encoding ascii 112 | } 113 | 114 | # Remove OST, sometimes there is an issue, so you can prevent. 115 | remove-item $DiskProfilePath\AppData\Local\Microsoft\Outlook\*.ost 116 | 117 | # Short delay and unmound the disk image 118 | Start-Sleep -Seconds 30 119 | Dismount-DiskImage -ImagePath $FSLDisk 120 | 121 | 122 | } 123 | Write-Output "--------------------------------------------------------------------" 124 | } 125 | -------------------------------------------------------------------------------- /FSLogix/FSLogix-UPMToProfileContainer.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script migrate from UPM to FSLogix Profile Container 4 | 5 | .DESCRIPTION 6 | Test before using!! 7 | 8 | .NOTES 9 | Version: 1.99 10 | Author: 11 | Rewrite Author: Manuel Winkel 12 | Creation Date: 2020-03-04 13 | Purpose/Change: 14 | #> 15 | ######################################################################################### 16 | # Setup Parameter first here newprofile oldprofile subfolder1 subfolder2 17 | # Requires -RunAsAdministrator 18 | # My Userprofiles come only with SAMAccount Name without Domain "\Username\2012R2\UPM_Profile 19 | ######################################################################################### 20 | # Example from my UPM Path "\\path_to_your_share\username\2012R2\UPM_Profile" 21 | # fslogix Root profile path 22 | $newprofilepath = "E:\FSLogix" 23 | # UPM Root profile path 24 | $oldprofilepath = "D:\OLDPROFILE" 25 | # Subfolder 1 - First Path to UPM_Profile Folder in UPM Profiles - see my example above 26 | #$urigfolder = "d:\Folder2" 27 | # Subfolder 2 - First Path to UPM_Profile Folder in UPM Profiles - see my example above 28 | $subfolder2 = "UPM_Profile" 29 | 30 | ######################################################################################### 31 | # Do not edit here 32 | ######################################################################################### 33 | $oldprofiles = Get-ChildItem $oldprofilepath | Select-Object -Expand fullname | Sort-Object | out-gridview -OutputMode Multiple -title "Select profile(s) to convert"| ForEach-Object{ 34 | Join-Path $_ $subfolder2 35 | } 36 | 37 | foreach ($old in $oldprofiles) { 38 | #$OldSplit = $old.split("\\") 39 | $OldStrings = ([regex]::Matches($old, "\\" )).count 40 | $OldSAM = $oldStrings - 1 41 | $sam = $old.split("\\")[$OldSAM] 42 | $sid = (New-Object System.Security.Principal.NTAccount($sam)).translate([System.Security.Principal.SecurityIdentifier]).Value 43 | $regtext = "Windows Registry Editor Version 5.00 44 | 45 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$sid] 46 | `"ProfileImagePath`"=`"C:\\Users\\$sam`" 47 | `"FSL_OriginalProfileImagePath`"=`"C:\\Users\\$sam`" 48 | `"Flags`"=dword:00000000 49 | `"State`"=dword:00000000 50 | `"ProfileLoadTimeLow`"=dword:00000000 51 | `"ProfileLoadTimeHigh`"=dword:00000000 52 | `"RefCount`"=dword:00000000 53 | `"RunLogonScriptSync`"=dword:00000000 54 | " 55 | 56 | $nfolder = join-path $newprofilepath ($sam) 57 | if (!(test-path $nfolder)) {New-Item -Path $nfolder -ItemType directory | Out-Null} 58 | & icacls $nfolder /setowner "$env:userdomain\$sam" /T /C 59 | & icacls $nfolder /grant $env:userdomain\$sam`:`(OI`)`(CI`)F /T 60 | $vhd = Join-Path $nfolder ("Profile_"+$sam+".vhdx") 61 | 62 | $script1 = "create vdisk file=`"$vhd`" maximum 30720 type=expandable" 63 | $script2 = "sel vdisk file=`"$vhd`"`r`nattach vdisk" 64 | $script3 = "sel vdisk file=`"$vhd`"`r`ncreate part prim`r`nselect part 1`r`nformat fs=ntfs quick" 65 | $script4 = "sel vdisk file=`"$vhd`"`r`nsel part 1`r`nassign letter=Y" 66 | $script5 = "sel vdisk file`"$vhd`"`r`ndetach vdisk" 67 | #$script6 = "sel vdisk file=`"$vhd`"`r`nattach vdisk readonly`"`r`ncompact vdisk" 68 | 69 | if (!(test-path $vhd)) { 70 | $script1 | diskpart 71 | start-process icacls "$vhd /setowner $sam" 72 | $script2 | diskpart 73 | Start-Sleep -s 5 74 | $script3 | diskpart 75 | $script4 | diskpart 76 | & label Y: Profile-$sam 77 | New-Item -Path Y:\Profile -ItemType directory | Out-Null 78 | Start-Sleep -s 5 79 | start-process icacls "Y:\Profile /setowner SYSTEM" 80 | Start-Process icacls -ArgumentList "Y:\Profile /inheritance:r" 81 | $cmd1 = "Y:\Profile /grant $env:userdomain\$sam`:`(OI`)`(CI`)F" 82 | Start-Process icacls -ArgumentList "Y:\Profile /grant SYSTEM`:`(OI`)`(CI`)F" 83 | Start-Process icacls -ArgumentList "Y:\Profile /grant Administrators`:`(OI`)`(CI`)F" 84 | Start-Process icacls -ArgumentList $cmd1 85 | Start-Sleep -s 5 86 | } else { 87 | 88 | $script2 | diskpart 89 | Start-Sleep -s 5 90 | $script4 | diskpart 91 | } 92 | <#$urigpathper = $urigfolder + "\" + "$sam" + "\" + "AppData" 93 | $urigpath = $urigfolder + "\" + "$sam" + "\" + "AppData" + "\*"#> 94 | "Copying $old to $vhd" 95 | Start-Sleep -s 5 96 | & robocopy $old Y:\Profile /MIR /B /R:1 /W:1 /COPYALL | Out-Null 97 | <#"Copying $urigpath to $vhd" 98 | Start-Sleep -s 5 99 | Copy-Item -Path "$urigpath" -Destination "Y:\Profile\AppData\Roaming" -Recurse -ErrorAction SilentlyContinue 100 | $urigpathpermission = Get-Acl -Path $urigpathper 101 | Set-Acl -AclObject $urigpathpermission -Path 'Y:\Profile\AppData\Roaming' 102 | dir -r 'Y:\Profile\AppData\Roaming' | Set-Acl -AclObject $urigpathpermission 103 | 104 | "Copying $urigpath2 to $vhd" 105 | $urigpathper2 = $urigfolder + "\" + "$sam" 106 | $urigpath2 = $urigfolder + "\" + "$sam" + "\*" 107 | Start-Sleep -s 5 108 | Copy-Item -Path "$urigpath2" -Destination "Y:\Profile" -Recurse -Exclude AppData -ErrorAction SilentlyContinue 109 | $urigpathpermission2 = Get-Acl -Path $urigpathper2 110 | Set-Acl -AclObject $urigpathpermission2 -Path 'Y:\Profile' 111 | dir -r 'Y:\Profile' | Set-Acl -AclObject $urigpathpermission2#> 112 | 113 | #Remove Windows Search Folder, to repair Windows Per User Search. Windows Search Folder is created automaticly and works after restart of Windows Search Service 114 | if (Test-Path "Y:\Profile\AppData\Roaming\Microsoft\Search") { 115 | Remove-Item -Path "Y:\Profile\AppData\Roaming\Microsoft\Search" -Force -Recurse | Out-Null 116 | } 117 | 118 | if (!(Test-Path "Y:\Profile\AppData\Local\FSLogix")) { 119 | New-Item -Path "Y:\Profile\AppData\Local\FSLogix" -ItemType directory | Out-Null 120 | } 121 | if (!(Test-Path "Y:\Profile\AppData\Local\FSLogix\ProfileData.reg")) {$regtext | Out-File "Y:\Profile\AppData\Local\FSLogix\ProfileData.reg" -Encoding ascii} 122 | $script5 | diskpart 123 | } -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/FSLogixMigration.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'PSGet_FSLogixMigration' 3 | # 4 | # Generated by: Dom Ruggeri, Jakub Podoba 5 | # 6 | # Generated on: 6/27/2019 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = '.\FSLogixMigration.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '1.0.0' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = '114b6fa2-9645-43bc-8878-90782e8f80a7' 22 | 23 | # Author of this module 24 | Author = 'Dom Ruggeri, Jakub Podoba' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'Sysgain, Inc' 28 | 29 | # Copyright statement for this module 30 | Copyright = '2019' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'Convert Roaming and UPD Profiles to FSLogix Profile Containers.' 34 | 35 | # Minimum version of the Windows PowerShell engine required by this module 36 | PowerShellVersion = '5.0' 37 | 38 | # Name of the Windows PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the Windows PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # CLRVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | # RequiredAssemblies = @() 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 72 | FunctionsToExport = 'Convert-RoamingProfile', 'Convert-UPDProfile', 'Copy-Profile', 73 | 'Get-ProfileSource', 'Mount-UPDProfile', 'New-MigrationObject', 74 | 'New-ProfileDisk', 'New-ProfileReg', 'Write-Log', 'Convert-UPMProfile' 75 | 76 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 77 | CmdletsToExport = @() 78 | 79 | # Variables to export from this module 80 | # VariablesToExport = @() 81 | 82 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 83 | AliasesToExport = @() 84 | 85 | # DSC resources to export from this module 86 | # DscResourcesToExport = @() 87 | 88 | # List of all modules packaged with this module 89 | # ModuleList = @() 90 | 91 | # List of all files packaged with this module 92 | # FileList = @() 93 | 94 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 95 | PrivateData = @{ 96 | 97 | PSData = @{ 98 | 99 | # Tags applied to this module. These help with module discovery in online galleries. 100 | # Tags = @() 101 | 102 | # A URL to the license for this module. 103 | # LicenseUri = '' 104 | 105 | # A URL to the main website for this project. 106 | # ProjectUri = '' 107 | 108 | # A URL to an icon representing this module. 109 | # IconUri = '' 110 | 111 | # ReleaseNotes of this module 112 | # ReleaseNotes = '' 113 | 114 | # External dependent modules of this module 115 | # ExternalModuleDependencies = '' 116 | 117 | } # End of PSData hashtable 118 | 119 | } # End of PrivateData hashtable 120 | 121 | # HelpInfo URI of this module 122 | # HelpInfoURI = '' 123 | 124 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 125 | # DefaultCommandPrefix = '' 126 | 127 | } 128 | 129 | -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/FSLogixMigration.psm1: -------------------------------------------------------------------------------- 1 | . "$PSScriptRoot\Main Functions\Convert-RoamingProfile.ps1" 2 | . "$PSScriptRoot\Main Functions\Convert-UPDProfile.ps1" 3 | . "$PSScriptRoot\Main Functions\Convert-UPMProfile.ps1" 4 | . "$PSScriptRoot\Helper Functions\Copy-Profile.ps1" 5 | . "$PSScriptRoot\Helper Functions\Get-ProfileSource.ps1" 6 | . "$PSScriptRoot\Helper Functions\Mount-UPDProfile.ps1" 7 | . "$PSScriptRoot\Helper Functions\New-MigrationObject.ps1" 8 | . "$PSScriptRoot\Helper Functions\New-ProfileDisk.ps1" 9 | . "$PSScriptRoot\Helper Functions\New-ProfileReg.ps1" 10 | . "$PSScriptRoot\Helper Functions\Write-Log.ps1" 11 | 12 | Write-Host " 13 | +---------------------------------------------------------+ 14 | + FSLogix Migration Module + 15 | +-------------------------------------------------------- + 16 | + Convert Roaming/UPD Profiles to FSLogix profile + 17 | + containers. + 18 | + + 19 | + To get started type Get-Help Convert-RoamingProfile + 20 | + or Get-Help Convert-UPDProfile + 21 | + + 22 | + Version: 1.0 + 23 | +---------------------------------------------------------+ 24 | " 25 | 26 | if (!(Get-Command Get-ADUser)){ 27 | Write-Warning "ActiveDirectory Module not found on this machine. This tool is required to migrate profiles." 28 | } 29 | if (!(Get-Command New-VHD)){ 30 | Write-Warning "Hyper-V Module not found on this machine. This tool is required to create VHDs." 31 | } 32 | if (!(Get-Module -ListAvailable Pester | Where-Object Version -GE 4.8.1)){ 33 | Write-Warning "Pester Module version 4.8.1 or higher was not found on this machine. This tool is required to run Pester Tests." 34 | } -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/Helper Functions/Copy-Profile.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This function mirrors a source directory to a target directory. 4 | 5 | .DESCRIPTION 6 | Copy-Profile uses Robocopy to copy/mirror a source directory to destination directory. 7 | Robocopy is run with the following arguments: /COPYALL /MIR /NP /NDL /NC /BYTES /NJH /NJS /XJ /R:0 (/TEE if -IncludeRobocopyDetail flag is set) 8 | A staging file is created and used to get the number of files, then used again to track progress in a write-Progress bar. 9 | The staging files are removed after use. 10 | Data is copied to the Destination Drive Specified, in a folder called "Profile" (As expected by FSLogix.) 11 | 12 | .PARAMETER Drive 13 | Drive is the Drive letter of the mounted VHD Destination Profile. This Drive is assumed to be in "D:\" format. 14 | 15 | .PARAMETER ProfilePath 16 | ProfilePath Parameter is the Source location. The immediate folder structure at this location will be copied to the destination Drive under folder "Profile" 17 | 18 | .PARAMETER IncludeRobocopyDetail 19 | The IncludeRobocopyDetail parameter will add /TEE to the argument list for the Robocopy function. The /TEE argument will output current file-by-file progress to the screen. 20 | 21 | .EXAMPLE 22 | Copy-Profile -ProfilePath C:\Users\User1 -Drive "D:\" 23 | 24 | The example above will mirror the folder structure of everything in C:\Users\User1 folder to D:\Profile\ 25 | 26 | .NOTES 27 | Author: Dom Ruggeri 28 | Last Edit: 7/23/2019 29 | 30 | #> 31 | Function Copy-Profile { 32 | 33 | [CmdletBinding(SupportsShouldProcess=$True)] 34 | Param ( 35 | [Parameter(ValueFromPipelineByPropertyName,Mandatory=$True)] 36 | [string]$Drive, 37 | 38 | [Parameter(ValueFromPipelineByPropertyName,Mandatory=$True)] 39 | [string]$ProfilePath, 40 | 41 | [Parameter()] 42 | [switch]$IncludeRobocopyDetail 43 | 44 | ) 45 | Begin { 46 | function Copy-WithProgress { 47 | [CmdletBinding()] 48 | param ( 49 | [Parameter(Mandatory = $true)] 50 | [string] $Source, 51 | 52 | [Parameter(Mandatory = $true)] 53 | [string] $Destination, 54 | 55 | [int] $Gap = 0, 56 | 57 | [int] $ReportGap = 20 58 | ) 59 | # Define regular expression that will gather number of bytes copied 60 | $RegexBytes = '(?<=\s+)\d+(?=\s+)' 61 | 62 | # Robocopy params 63 | # COPYALL = Copy all file ownership and permission info 64 | # MIR = Mirror mode 65 | # NP = Don't show progress percentage in log 66 | # NC = Don't log file classes (existing, new file, etc.) 67 | # BYTES = Show file sizes in bytes 68 | # NJH = Do not display robocopy job header (JH) 69 | # NJS = Do not display robocopy job summary (JS) 70 | # TEE = Display log in stdout AND in target log file 71 | If ($IncludeRobocopyDetail){ 72 | $CommonRobocopyParams = '/COPYALL /MIR /NP /NDL /NC /BYTES /NJH /NJS /XJ /TEE /R:0' 73 | } 74 | Else{ 75 | $CommonRobocopyParams = '/COPYALL /MIR /NP /NDL /NC /BYTES /NJH /NJS /XJ /R:0' 76 | } 77 | $CommonRobocopyExcludes = "/XD ""$ProfilePath`$RECYCLE.BIN"" ""$ProfilePath`System Volume Information""" 78 | 79 | Write-Output 'Analyzing robocopy job ...' 4>&1 | Write-Log -LogPath $LogPath 80 | $StagingLogPath = '{0}\temp\{1} robocopy staging.log' -f $env:windir, (Get-Date -Format 'yyyy-MM-dd HH-mm-ss') 81 | 82 | $StagingArgumentList = '"{0}" "{1}" /LOG:"{2}" /L {3} {4}' -f $Source, $Destination, $StagingLogPath, $CommonRobocopyParams, $CommonRobocopyExcludes 83 | Write-Output ('Staging arguments: {0}' -f $StagingArgumentList) 4>&1 | Write-Log -LogPath $LogPath 84 | Start-Process -Wait -FilePath robocopy.exe -ArgumentList $StagingArgumentList -NoNewWindow 85 | # Get the total number of files that will be copied 86 | $StagingContent = Get-Content -Path $StagingLogPath 87 | $TotalFileCount = $StagingContent.Count - 1 88 | 89 | # Get the total number of bytes to be copied 90 | [RegEx]::Matches(($StagingContent -join "`n"), $RegexBytes) | ForEach-Object { $BytesTotal = 0; } { $BytesTotal += $_.Value; } 91 | Write-Output ('Total bytes to be copied: {0}' -f $BytesTotal) 4>&1 | Write-Log -LogPath $LogPath 92 | 93 | # Begin the robocopy process 94 | $RobocopyLogPath = '{0}\temp\{1} robocopy.log' -f $env:windir, (Get-Date -Format 'yyyy-MM-dd HH-mm-ss') 95 | $ArgumentList = '"{0}" "{1}" /LOG:"{2}" /ipg:{3} {4} {5}' -f $Source, $Destination, $RobocopyLogPath, $Gap, $CommonRobocopyParams, $CommonRobocopyExcludes 96 | Write-Output ('Beginning the robocopy process with arguments: {0}' -f $ArgumentList) 4>&1 | Write-Log -LogPath $LogPath 97 | $Robocopy = Start-Process -FilePath robocopy.exe -ArgumentList $ArgumentList -Verbose -PassThru -NoNewWindow 98 | Start-Sleep -Milliseconds 100 99 | 100 | # Progress bar loop 101 | while (!$Robocopy.HasExited) { 102 | Start-Sleep -Milliseconds $ReportGap 103 | $BytesCopied = 0 104 | $LogContent = Get-Content -Path $RobocopyLogPath 105 | $BytesCopied = [Regex]::Matches($LogContent, $RegexBytes) | ForEach-Object -Process { $BytesCopied += $_.Value; } -End { $BytesCopied; } 106 | $CopiedFileCount = $LogContent.Count - 1 107 | $Percentage = 0 108 | if ($BytesCopied -gt 0) { 109 | $Percentage = (($BytesCopied/$BytesTotal)*100) 110 | } 111 | Write-Progress -Activity Robocopy -Status ("Copied {0} of {1} files; Copied {2} of {3} bytes" -f $CopiedFileCount, $TotalFileCount, $BytesCopied, $BytesTotal) -PercentComplete $Percentage 112 | } 113 | 114 | Remove-Item $StagingLogPath 115 | Remove-Item $RobocopyLogPath 116 | 117 | [PSCustomObject]@{ 118 | BytesCopied = $BytesCopied 119 | FilesCopied = $CopiedFileCount 120 | } 121 | } 122 | } 123 | 124 | Process { 125 | if ($pscmdlet.ShouldProcess($Name, 'Action')){ 126 | $Destination = "$Drive"+"Profile\" 127 | Write-Output "Beginning copy from $ProfilePath to $Destination." 4>&1 | Write-Log -LogPath $LogPath 128 | Copy-WithProgress -Source "$ProfilePath " -Destination "$Destination " 129 | } 130 | } 131 | 132 | End { 133 | } 134 | } -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/Helper Functions/Get-ProfileSource.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This function takes an input source, gathers profiles in the source, and creates an object with profiles. 4 | 5 | .DESCRIPTION 6 | This function takes direct input from Convert-RoamingProfile, using the source type and source location, 7 | to gather a list of profiles. It then outputs a PowerShell object with those profiles. 8 | 9 | .PARAMETER SourceType 10 | Specify Path, ParentPath, or CSV to identify the source type. 11 | 12 | .PARAMETER Source 13 | CSV or File Path to profiles. 14 | 15 | .EXAMPLE 16 | Get-ProfileSource -SourceType ParentPath -Source "C:\Users" 17 | The example above adds the "C:\Users" path, and gathers all profiles within it. 18 | 19 | .EXAMPLE 20 | Get-ProfileSource -SourceType Path -Source "C:\Users\username" 21 | The example above adds the "C:\Users\username" path, and adds the "username" profile. 22 | 23 | .EXAMPLE 24 | Get-ProfileSource -SourceType CSV -Source "C:\temp\pathlist.csv" 25 | The example above imports a CSV located at C:\temp\pathlist.csv, and adds the paths in the file. 26 | 27 | 28 | .NOTES 29 | Author: Dom Ruggeri 30 | Last Edit: 06/12/2019 31 | 32 | #> 33 | Function Get-ProfileSource { 34 | 35 | [CmdletBinding(SupportsShouldProcess=$True,DefaultParameterSetName='ParentPath')] 36 | Param ( 37 | 38 | # parameters as ParentPath','Path','CSV' 39 | 40 | [Parameter(ValueFromPipelineByPropertyName,Mandatory=$True,ParameterSetName='ParentPath')] 41 | [string]$ParentPath, 42 | 43 | [Parameter(ValueFromPipelineByPropertyName,Mandatory=$True,ParameterSetName='ProfilePath')] 44 | [string]$ProfilePath, 45 | 46 | [Parameter(ValueFromPipelineByPropertyName,Mandatory=$True,ParameterSetName='CSV')] 47 | [string]$CSV 48 | ) 49 | 50 | Begin { 51 | $OutputObject = @() 52 | } 53 | 54 | Process { 55 | if ($ParentPath) { 56 | if ($pscmdlet.ShouldProcess($ParentPath, 'Import')){ 57 | $PathList = ((Get-ChildItem $ParentPath).FullName) 58 | foreach ($Path in $PathList){ 59 | $Item = New-Object system.object 60 | $Item | Add-Member -Type NoteProperty -Name ProfilePath -Value $Path 61 | $OutputObject += $Item 62 | } 63 | } 64 | } 65 | 66 | if ($ProfilePath) { 67 | if ($pscmdlet.ShouldProcess($ProfilePath, 'Import')){ 68 | $Item = New-Object system.object 69 | $Item | Add-Member -Type NoteProperty -Name ProfilePath -Value ((Get-Item $ProfilePath).FullName) 70 | $OutputObject += $Item 71 | } 72 | } 73 | 74 | if ($CSV) { 75 | if ($pscmdlet.ShouldProcess($CSV, 'Import')){ 76 | if (!((Import-Csv $CSV).Path)){ 77 | write-host "No Path header" 78 | } 79 | $PathList = (Import-Csv $CSV).Path 80 | foreach ($Path in $PathList){ 81 | $Item = New-Object system.object 82 | $Item | Add-Member -Type NoteProperty -Name ProfilePath -Value $Path 83 | $OutputObject += $Item 84 | } 85 | } 86 | } 87 | } 88 | 89 | End { 90 | $OutputObject 91 | } 92 | } -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/Helper Functions/Mount-UPDProfile.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Mounts the UPD Profile [VHD/x] as a disk in system and creates an object with results of the drive letter and file path mounted file 4 | 5 | .DESCRIPTION 6 | Checks if .vhdx file exists and mounts the UPD Profile (VHD) as a disk in system with random drive letter. 7 | After successful mount operation the function will return an object with two keys: DriveLetter, VHDX 8 | 9 | .PARAMETER VHDX 10 | Path to .vhdx file 11 | 12 | .EXAMPLE 13 | Get-UPDProfile -ProfilePath E:\UPDProfiles | Mount-UPDProfile 14 | 15 | Drive VHDX 16 | ----- ---- 17 | G:\ E:\UPDProfiles\UVHD-S-1-5-21-3286950516-3440391731-2706545478-1155.vhdx 18 | H:\ E:\UPDProfiles\UVHD-S-1-5-21-3286950516-3440391731-2706545478-1195.vhdx 19 | I:\ E:\UPDProfiles\UVHD-S-1-5-21-3286950516-3440391731-2706545478-1199.vhdx 20 | J:\ E:\UPDProfiles\UVHD-S-1-5-21-3286950516-3440391731-2706545478-1246.vhdx 21 | 22 | .NOTES 23 | Author: Jakub Podoba 24 | Last Edit: 06/18/2019 25 | 26 | #> 27 | Function Mount-UPDProfile { 28 | 29 | [CmdletBinding(SupportsShouldProcess = $True)] 30 | Param ( 31 | [Parameter(ValueFromPipelineByPropertyName, Mandatory = $True)] 32 | [object]$ProfilePath 33 | ) 34 | 35 | Begin { 36 | $OutputObject = @() 37 | } 38 | 39 | Process { 40 | $VHDX = $ProfilePath 41 | if ($pscmdlet.ShouldProcess($Name, 'Action')) { 42 | 43 | Write-Output "Checking if file $VHDX exists." | Write-Log -LogPath $LogPath 44 | if ((Test-Path -Path $VHDX) -eq $True) { 45 | Write-Output "File $VHDX exists." | Write-Log -LogPath $LogPath 46 | 47 | try { 48 | Write-Output "Mounting the file $VHDX" | Write-Log -LogPath $LogPath 49 | $DriveLetter = (Mount-VHD -Path $VHDX -PassThru | Get-Disk | Get-Partition | select -Last 1).DriveLetter 50 | Write-Output "File $VHDX mounted successfully." | Write-Log -LogPath $LogPath 51 | Write-Output "Mounted to $driveletter`:\" | Write-Log -LogPath $LogPath 52 | } 53 | catch { 54 | Write-Error "Unable to mount $VHDX, because following error: `n$_" 2>&1 | Write-Log -LogPath $LogPath 55 | } 56 | 57 | $Item = New-Object system.object 58 | $Item | Add-Member -Type NoteProperty -Name Drive -Value "$DriveLetter`:\" 59 | $Item | Add-Member -Type NoteProperty -Name VHDX -Value $VHDX 60 | $OutputObject += $Item 61 | 62 | } 63 | else { 64 | Write-Error "$VHDX file does not exist" 2>&1 | Write-Log -LogPath $LogPath 65 | } 66 | } 67 | } 68 | 69 | End { 70 | $OutputObject 71 | } 72 | } -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/Helper Functions/New-MigrationObject.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This function reads a path name and target, parses out the username in path, and creates an object with the needed target destination and username. 4 | 5 | .DESCRIPTION 6 | This Function takes direct input from Get-Profile, and parses a username from the path. 7 | It also expects a Target destination, assumed to be the FSLogix Profile share. 8 | It then parses the username out of the source path, finds the SID, (Or vice versa if SID is in path) and outputs a PowerShell Object. 9 | A Target destination is also calculated in this function, using the expected format for FSLogix. 10 | 11 | .PARAMETER ProfilePath 12 | The ProfilePath parameter can be an object of paths, or a single specified path. 13 | 14 | .PARAMETER Target 15 | The Target is the destination FSLogix profile share. A new, user-specific target is generated with the parsed information from the path. 16 | 17 | .PARAMETER VHD 18 | By default, a VHDX will be the expected output for a target. If the VHD flag is set, a VHD will be created instead. 19 | 20 | .EXAMPLE 21 | New-MigrationObject -ProfilePath "C:\users\User1.V2" -Target "\\Server\FSLogixProfiles$" 22 | 23 | ProfilePath : C:\users\User1.V2\ 24 | Username : User1 25 | Version : V2 26 | UserSID : S-1-5-21-726503766-34464521-262356478-12241 27 | UserGUID : e53615b1-a494-4edf-af94-13557fa591a8 28 | Target : \\Server\FSLogixProfiles$\S-1-5-21-726503766-34464521-262356478-12241_User1\Profile_User1.vhdx 29 | 30 | The example above takes a profile path, and generates a username from it, generates a destination based on input target, and creates an object. 31 | 32 | .EXAMPLE 33 | New-MigrationObject -ProfilePath "C:\users\User1.V2" -Target "\\Server\FSLogixProfiles$" -VHD 34 | 35 | ProfilePath : C:\users\User1.V2\ 36 | Username : User1 37 | Version : V2 38 | UserSID : S-1-5-21-726503766-34464521-262356478-12241 39 | UserGUID : e53615b1-a494-4edf-af94-13557fa591a8 40 | Target : \\Server\FSLogixProfiles$\S-1-5-21-726503766-34464521-262356478-12241_User1\Profile_User1.vhd 41 | 42 | The example above does the same thing as Example 1, but with a VHD as the output target. 43 | 44 | .NOTES 45 | Author: Dom Ruggeri 46 | Last Edit: 06/27/2019 47 | 48 | #> 49 | Function New-MigrationObject { 50 | 51 | [CmdletBinding(SupportsShouldProcess=$True)] 52 | Param ( 53 | [Parameter(ValueFromPipelineByPropertyName,Mandatory=$True)] 54 | [string]$ProfilePath, 55 | 56 | [Parameter(ValueFromPipelineByPropertyName,Mandatory=$True)] 57 | [string]$Target, 58 | 59 | [Parameter(ValueFromPipelineByPropertyName)] 60 | [switch]$VHD 61 | ) 62 | 63 | Begin { 64 | $SIDRegex = "S-\d-\d+-(\d+-){1,14}\d+" 65 | $VersionRegex = "(?i)(\.V\d)" 66 | $OutputObject = @() 67 | } 68 | 69 | Process { 70 | if ($pscmdlet.ShouldProcess($ProfilePath, 'Parsing')){ 71 | 72 | $Split = (split-path $ProfilePath -Leaf) 73 | if ($ProfilePath){ 74 | if ($Split -match $VersionRegex){ 75 | $Username = ($Split -split $VersionRegex)[0] 76 | $Version = ($Split).Replace("$Username.","") 77 | } 78 | else{ 79 | $Version = "none" 80 | } 81 | 82 | if ($Split -match $SIDRegex){ 83 | $UserSID = ($Split | Select-String -Pattern $SIDRegex).matches.groups.value[0] 84 | try { 85 | $Username = (Get-ADUser -identity $UserSID).SamAccountName 86 | } 87 | catch { 88 | $Username = "Not Found" 89 | } 90 | } 91 | Else{ 92 | $Username = ($Split -split $VersionRegex)[0] 93 | } 94 | 95 | try { 96 | $UserSID = (Get-ADUser $Username -Properties SID).SID 97 | } 98 | catch { 99 | $UserSID = "SID Not Found" 100 | } 101 | try { 102 | $UserGUID = (Get-ADUser $Username -Properties ObjectGUID).ObjectGUID 103 | } 104 | catch { 105 | $UserGUID = "GUID Not Found" 106 | } 107 | if($VHD){ 108 | $Extension = ".vhd" 109 | } 110 | else{ 111 | $Extension = ".vhdx" 112 | } 113 | 114 | if (($Target.ToString().ToCharArray() | Select-Object -Last 1) -ne "\"){ 115 | $Target = $Target+"\" 116 | } 117 | if ($UserSID -ne "SID Not Found"){ 118 | $NewTarget = $Target+$UserSID+"_"+$Username+"\Profile_"+$Username+$Extension 119 | } 120 | else { 121 | $NewTarget = "Cannot Copy" 122 | } 123 | } 124 | $Item = New-Object system.object 125 | $Item | Add-Member -Type NoteProperty -Name ProfilePath -Value $ProfilePath 126 | $Item | Add-Member -Type NoteProperty -Name Username -Value $Username 127 | $Item | Add-Member -Type NoteProperty -Name Version -Value $Version 128 | $Item | Add-Member -Type NoteProperty -Name UserSID -Value $UserSID 129 | $Item | Add-Member -Type NoteProperty -Name UserGUID -Value $UserGUID 130 | $Item | Add-Member -Type NoteProperty -Name Target -Value $NewTarget 131 | $OutputObject += $Item 132 | } 133 | } 134 | 135 | End { 136 | $OutputObject 137 | } 138 | } -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/Helper Functions/Write-Log.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Creating new logfile in specified location and writes message to specified log file 4 | 5 | .DESCRIPTION 6 | Creates a log file with the path and name specified in the parameters. Checks if log file exists, and if it does deletes it and creates a new one. 7 | Once created, writes initial logging data 8 | 9 | .PARAMETER LogPath 10 | Mandatory. Full path of the log file you want to write to. Example: C:\Windows\Temp\Test_Script.log 11 | 12 | .PARAMETER Message 13 | Mandatory. The string that you want to write to the log 14 | 15 | .EXAMPLE 16 | Write-Log -LogPath "C:\Windows\Temp\Log.txt" -Message 17 | Writes a new log message to a new line in the specified log file. 18 | 19 | New-ProfileReg -UserSID 'S-1-5-21-3286950516-3440391731-2706545478-1155' -Drive F:\ -Verbose 4>&1 | Write-Log -LogPath "C:\Windows\Temp\Log.txt" 20 | Writes all verbose from function to specified log file. 21 | 22 | .NOTES 23 | Author: Jakub Podoba 24 | Last Edit: 06/16/2019 25 | 26 | #> 27 | Function Write-Log { 28 | 29 | [CmdletBinding(SupportsShouldProcess = $True)] 30 | Param ( 31 | [Parameter(Mandatory = $False)] 32 | [string]$LogPath, 33 | 34 | [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory = $True)] 35 | [string[]]$Message 36 | ) 37 | 38 | Begin { 39 | if ($LogPath){ 40 | if (!(Test-Path -Path $LogPath) ) { 41 | New-Item -Path $LogPath -ItemType File | Out-Null 42 | 43 | Add-Content -Path $LogPath -Value "***************************************************************************************************" 44 | Add-Content -Path $LogPath -Value "$([DateTime]::Now) - Started processing" 45 | Add-Content -Path $LogPath -Value "***************************************************************************************************" 46 | Add-Content -Path $LogPath -Value "`n" 47 | } 48 | } 49 | } 50 | 51 | Process { 52 | if ($pscmdlet.ShouldProcess($Name, 'Action')) { 53 | $Message | ForEach-Object { 54 | if ($LogPath){ 55 | Add-Content -Path $LogPath -Value "$([DateTime]::Now) - $_" 56 | } 57 | Write-Verbose "$([DateTime]::Now) - $_" 58 | } 59 | } 60 | } 61 | 62 | End { 63 | } 64 | } -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/PesterRunTests.ps1: -------------------------------------------------------------------------------- 1 | $rootPath = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | 3 | Set-Location $rootPath 4 | 5 | Import-Module Pester 6 | 7 | Invoke-Pester "$rootPath\Tests\FSLogixMigration.Test.ps1" 8 | 9 | Invoke-Pester "$rootPath\Tests\Copy-Profile.Test.ps1" 10 | 11 | Invoke-Pester "$rootPath\Tests\Get-ProfileSource.Test.ps1" 12 | 13 | Invoke-Pester "$rootPath\Tests\Mount-UPDProfile.Test.ps1" 14 | 15 | Invoke-Pester "$rootPath\Tests\New-MigrationObject.Test.ps1" 16 | 17 | Invoke-Pester "$rootPath\Tests\New-ProfileDisk.Test.ps1" 18 | 19 | Invoke-Pester "$rootPath\Tests\New-ProfileReg.Test.ps1" 20 | 21 | Invoke-Pester "$rootPath\Tests\Write-Log.Test.ps1" 22 | 23 | Invoke-Pester "$rootPath\Tests\Convert-RoamingProfile.Test.ps1" -Tag 'Acceptance' 24 | 25 | Invoke-Pester "$rootPath\Tests\Convert-UPDProfile.Test.ps1" -Tag 'Acceptance' 26 | 27 | #Invoke-Pester "$rootPath\Tests\Convert-RoamingProfile.Test.ps1" -Tag 'Load' -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/Tests/Copy-Profile.Test.ps1: -------------------------------------------------------------------------------- 1 | $rootPath = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | 3 | $functionName = ($MyInvocation.MyCommand.Name).Split(".")[0] 4 | 5 | Get-Module FSLogixMigration -ErrorAction SilentlyContinue | Remove-Module -Force 6 | 7 | Import-Module "$rootPath\..\FSLogixMigration.psd1" -Force 8 | 9 | Describe -Tag 'Acceptance' "$functionName Acceptance Test" { 10 | Context "Test Function $functionName" { 11 | It "$functionName has the correct Parameters" { 12 | Get-Command $functionName | Should -HaveParameter Drive -Mandatory 13 | Get-Command $functionName | Should -HaveParameter ProfilePath -Mandatory 14 | Get-Command $functionName | Should -HaveParameter IncludeRobocopyDetail -Not -Mandatory 15 | } 16 | 17 | It "$functionName has the correct private functions" { 18 | "$rootPath\..\Helper Functions\$functionName.ps1" | Should -FileContentMatch "function Copy-WithProgress" 19 | } 20 | 21 | It "$functionName has to copy and check file" { 22 | $Path = "$($env:TEMP)\test" 23 | if (Test-Path -Path "$Path") { 24 | Remove-Item -Path "$Path" -Force -Recurse -Confirm:$false 25 | } 26 | New-Item -Path "$Path\source\test.txt" -ItemType File -Force 27 | $sourcePath = "$Path\source\" 28 | $destinationPath = "$Path\" 29 | Copy-Profile -Drive $destinationPath -ProfilePath $sourcePath 30 | "$Path\Profile\test.txt" | Should -Exist 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/Tests/FSLogixMigration.Test.ps1: -------------------------------------------------------------------------------- 1 | Clear-Host 2 | $rootPath = Split-Path -Parent $MyInvocation.MyCommand.Path 3 | 4 | $moduleName = 'FSLogixMigration' 5 | 6 | Describe "$moduleName Module Test" { 7 | 8 | Context 'Module Setup' { 9 | It "has the root module $moduleName.psm1" { 10 | "$rootPath\..\$moduleName.psm1" | Should Exist 11 | } 12 | 13 | It "has the a manifest file of $moduleName.psm1" { 14 | "$rootPath\..\$moduleName.psd1" | Should Exist 15 | "$rootPath\..\$moduleName.psd1" | Should -FileContentMatch "$moduleName.psm1" 16 | } 17 | 18 | It "$moduleName folder has Helper Functions" { 19 | "$rootPath\..\Helper Functions\*.ps1" | Should Exist 20 | } 21 | 22 | It "$moduleName folder has Main Functions" { 23 | "$rootPath\..\Main Functions\*.ps1" | Should Exist 24 | } 25 | 26 | It "$moduleName is valid Powershell code" { 27 | $psFile = Get-Content -Path "$rootPath\..\$moduleName.psm1" -ErrorAction Stop 28 | 29 | $errors = $null 30 | $null = [System.Management.Automation.PSParser]::Tokenize($psFile, [ref]$errors) 31 | $errors.Count | Should Be 0 32 | } 33 | } 34 | 35 | $functions = ( 36 | 'Copy-Profile', 37 | 'Get-ProfileSource', 38 | 'New-MigrationObject', 39 | 'Mount-UPDProfile', 40 | 'New-ProfileDisk', 41 | 'New-ProfileReg', 42 | 'Write-Log', 43 | 'Convert-RoamingProfile', 44 | 'Convert-UPDProfile' 45 | ) 46 | 47 | foreach ($function in $functions) { 48 | 49 | switch ($function) { 50 | { 51 | ($_ -eq 'Copy-Profile') -or 52 | ($_ -eq 'New-MigrationObject') -or 53 | ($_ -eq 'Get-ProfileSource') -or 54 | ($_ -eq 'Mount-UPDProfile') -or 55 | ($_ -eq 'New-ProfileDisk') -or 56 | ($_ -eq 'New-ProfileReg') -or 57 | ($_ -eq 'Write-Log') 58 | 59 | } { $tmpPath = "Helper Functions" } 60 | 61 | { 62 | ($_ -eq 'Convert-RoamingProfile') -or 63 | ($_ -eq 'Convert-UPDProfile') 64 | } { $tmpPath = "Main Functions" } 65 | } 66 | 67 | Context "Test Function $function" { 68 | It "$function.ps1 should exist" { 69 | "$rootPath\..\$tmpPath\$function.ps1" | Should Exist 70 | } 71 | 72 | It "$function.ps1 should have a help block" { 73 | "$rootPath\..\$tmpPath\$function.ps1" | Should -FileContentMatch "<#" 74 | "$rootPath\..\$tmpPath\$function.ps1" | Should -FileContentMatch "#>" 75 | } 76 | 77 | It "$function.ps1 should have a SYNOPSIS section in the help block" { 78 | "$rootPath\..\$tmpPath\$function.ps1" | Should -FileContentMatch ".SYNOPSIS" 79 | } 80 | 81 | It "$function.ps1 should have a DESCRIPTION section in the help block" { 82 | "$rootPath\..\$tmpPath\$function.ps1" | Should -FileContentMatch ".DESCRIPTION" 83 | } 84 | 85 | It "$function.ps1 should have a PARAMETER section in the help block" { 86 | "$rootPath\..\$tmpPath\$function.ps1" | Should -FileContentMatch ".PARAMETER" 87 | } 88 | 89 | It "$function.ps1 should have a EXAMPLE section in the help block" { 90 | "$rootPath\..\$tmpPath\$function.ps1" | Should -FileContentMatch ".EXAMPLE" 91 | } 92 | 93 | It "$function.ps1 should have a NOTES section in the help block" { 94 | "$rootPath\..\$tmpPath\$function.ps1" | Should -FileContentMatch ".NOTES" 95 | } 96 | 97 | It "$function.ps1 should be an advanced function" { 98 | "$rootPath\..\$tmpPath\$function.ps1" | Should -FileContentMatch "function" 99 | "$rootPath\..\$tmpPath\$function.ps1" | Should -FileContentMatch "CmdletBinding" 100 | "$rootPath\..\$tmpPath\$function.ps1" | Should -FileContentMatch "Param" 101 | } 102 | 103 | It "$function.ps1 should support -WhatIf parameter" { 104 | "$rootPath\..\$tmpPath\$function.ps1" | Should -FileContentMatch "SupportsShouldProcess" 105 | } 106 | 107 | It "$function.ps1 should support -Verbose parameter" { 108 | "$rootPath\..\$tmpPath\$function.ps1" | Should -FileContentMatch "Write-Verbose" 109 | } 110 | 111 | It "$function.ps1 is valid Powershell code" { 112 | $psFile = Get-Content -Path "$rootPath\..\$tmpPath\$function.ps1" -ErrorAction Stop 113 | 114 | $errors = $null 115 | $null = [System.Management.Automation.PSParser]::Tokenize($psFile, [ref]$errors) 116 | $errors.Count | Should Be 0 117 | } 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/Tests/Generate-RandomFiles.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Generate files with random binary data. 4 | 5 | .DESCRIPTION 6 | Random files are useful for testing synchronization processes, backup/restore 7 | and anything in general that handles large quantities of file data. 8 | 9 | This script specifically does not create sparse files, or files with zeros or 10 | other constant data. Such data is easily compressible or can be optimize during transfer. 11 | Instead, the script generates pseudo-random data that cannot be optimized. The .NET 12 | random generator used here is quite fast with about 100 MB/s. Writing the data 13 | is usually the bottleneck. 14 | 15 | Some notes on usage: by default, about 15 files, 100 MB total is generated in the 16 | current directory. If you specify a TargetPath, it must exist. File sizes are random betwee 17 | Minfilesize and Maxfilesize; these are allowed to be equal, generating fixed-size files. 18 | Also, the file timestamps are randomized between and 24h ago, to give 19 | synchronization algorithms something to work with. Filenames are generated by 20 | randomly shuffling the "filenameseed" string, and always end with ".bin". 21 | 22 | .EXAMPLE 23 | .\Generate-RandomFiles -Targetpath c:\temp\Randomdata 24 | 25 | .EXAMPLE 26 | .\Generate-RandomFiles -Targetpath c:\temp\Randomdata -minfilesize 100MB -maxfilesize 100MB -totalsize 10GB -timerangehours 0 27 | 28 | .NOTES 29 | Author: Jakub Podoba 30 | Last Edit: 06/29/2019 31 | 32 | #> 33 | Function Generate-RandomFiles { 34 | 35 | [CmdletBinding(SupportsShouldProcess = $True)] 36 | Param ( 37 | [String] $TargetPath = $((Get-Location).Path), 38 | [int64] $minfilesize = 1MB, 39 | [int64] $maxfilesize = 1GB, 40 | [int64] $totalsize = 10GB, 41 | [int] $timerangehours = 24, 42 | [string] $filenameseed = "abcdefghijkl012345" 43 | ) 44 | 45 | Begin { 46 | if (-not (Split-Path -IsAbsolute $TargetPath)) { 47 | $TargetPath = Join-Path (Get-Location).Path $TargetPath 48 | } 49 | if (-not (Test-Path -Path $TargetPath -PathType Container )) { 50 | throw "TargetPath '$TargetPath' does not exist or is not a directory" 51 | } 52 | 53 | $currentsize = [int64]0 54 | $currentime = Get-Date 55 | } 56 | 57 | Process { 58 | while ($currentsize -lt $totalsize) { 59 | if ($minfilesize -lt $maxfilesize) { 60 | $filesize = Get-Random -Minimum $minfilesize -Maximum $maxfilesize 61 | } 62 | else { 63 | $filesize = $maxfilesize 64 | } 65 | if ($currentsize + $filesize -gt $totalsize) { 66 | $filesize = $totalsize - $currentsize 67 | } 68 | $currentsize += $filesize 69 | 70 | $data = new-object byte[] $filesize 71 | (new-object Random).NextBytes($data) 72 | 73 | $filename = ($filenameseed.ToCharArray() | Get-Random -Count ($filenameseed.Length)) -join '' 74 | $path = Join-Path $TargetPath "$($filename).bin" 75 | 76 | try { 77 | [IO.File]::WriteAllBytes($path, $data) 78 | if ($timerangehours -gt 0) { 79 | $timestamp = $currentime.AddHours(-1 * (Get-Random -Minimum 0 -Maximum $timerangehours)) 80 | } 81 | else { 82 | $timestamp = $currentime 83 | } 84 | $fileobject = Get-Item -Path $path 85 | $fileobject.CreationTime = $timestamp 86 | $fileobject.LastWriteTime = $timestamp 87 | 88 | [pscustomobject] @{ 89 | filename = $path 90 | timestamp = $timestamp 91 | datasize = $filesize 92 | } 93 | } 94 | catch { 95 | $message = "failed to write data to $path, error $($_.Exception.Message)" 96 | Throw $message 97 | } 98 | } 99 | } 100 | 101 | End { 102 | } 103 | } -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/Tests/Get-ProfileSource.Test.ps1: -------------------------------------------------------------------------------- 1 | $rootPath = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | 3 | $functionName = ($MyInvocation.MyCommand.Name).Split(".")[0] 4 | 5 | Get-Module FSLogixMigration -ErrorAction SilentlyContinue | Remove-Module -Force 6 | 7 | Import-Module "$rootPath\..\FSLogixMigration.psd1" -Force 8 | 9 | Describe -Tag 'Acceptance' "$functionName Acceptance Test" { 10 | Context "Test Function $functionName" { 11 | It "$functionName has the correct Parameters" { 12 | Get-Command $functionName | Should -HaveParameter ParentPath -Mandatory 13 | Get-Command $functionName | Should -HaveParameter ProfilePath -Mandatory 14 | Get-Command $functionName | Should -HaveParameter CSV -Mandatory 15 | } 16 | 17 | It "$functionName has to gathers all profiles by ParentPath" { 18 | $Path = "C:\Users" 19 | Get-ProfileSource -ParentPath $Path | Should -Not -BeNullOrEmpty 20 | } 21 | 22 | It "$functionName has to fail by not existing ParentPath" { 23 | $Path = "C:\UsersXXXXXX" 24 | $errorThrown = $false; 25 | try { 26 | Get-ProfileSource -ParentPath $Path -ErrorAction Stop 27 | } 28 | catch { 29 | $errorThrown = $true 30 | } 31 | $errorThrown | Should Be $true 32 | } 33 | 34 | It "$functionName has to gathers profile by ProfilePath" { 35 | $Path = "C:\Users" 36 | $profiles = Get-ChildItem -Path $Path | select -Last 1 37 | Get-ProfileSource -ParentPath $profiles.FullName | Should -Not -BeNullOrEmpty 38 | } 39 | 40 | It "$functionName has to gathers all profiles by CSV" { 41 | $Path = "C:\Users" 42 | $profiles = Get-ChildItem -Path $Path | select @{ Label = "Path"; Expression = { $_.FullName } } | Export-Csv "$($env:TEMP)\test.csv" 43 | Get-ProfileSource -CSV "$($env:TEMP)\test.csv" | Should -Not -BeNullOrEmpty 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/Tests/Mount-UPDProfile.Test.ps1: -------------------------------------------------------------------------------- 1 | $rootPath = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | 3 | $functionName = ($MyInvocation.MyCommand.Name).Split(".")[0] 4 | 5 | Get-Module FSLogixMigration -ErrorAction SilentlyContinue | Remove-Module -Force 6 | 7 | Import-Module "$rootPath\..\FSLogixMigration.psd1" -Force 8 | 9 | Describe -Tag 'Acceptance' "$functionName Acceptance Test" { 10 | Context "Test Function $functionName" { 11 | 12 | $vhdxPath = "$($env:TEMP)\test.vhdx" 13 | $newDrive = (New-VHD -path $vhdxPath -SizeBytes 150MB -Dynamic -LogicalSectorSizeBytes 512 | 14 | Mount-VHD -Passthru | ` 15 | get-disk -number { $_.DiskNumber } | ` 16 | Initialize-Disk -PartitionStyle GPT -PassThru | ` 17 | New-Partition -UseMaximumSize -AssignDriveLetter:$False | ` 18 | Format-Volume -Confirm:$false -FileSystem NTFS -force | ` 19 | get-partition | ` 20 | Add-PartitionAccessPath -AssignDriveLetter -PassThru | ` 21 | get-volume).DriveLetter 22 | 23 | Write-Verbose "Dismounting VHD." 24 | Dismount-VHD $vhdxPath 25 | 26 | It "$functionName has the correct Parameters" { 27 | Get-Command $functionName | Should -HaveParameter ProfilePath -Mandatory 28 | } 29 | 30 | It "$functionName has to mount .vhdx on as disk and compare drive letter if are the same" { 31 | $tmpDrive = Mount-UPDProfile -ProfilePath $vhdxPath 32 | "$($newDrive):\" -eq $tmpDrive.Drive | Should -Be $true 33 | } 34 | Dismount-VHD $vhdxPath 35 | Remove-Item $vhdxPath -Force -Confirm:$false 36 | } 37 | } -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/Tests/New-MigrationObject.Test.ps1: -------------------------------------------------------------------------------- 1 | $rootPath = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | 3 | $functionName = ($MyInvocation.MyCommand.Name).Split(".")[0] 4 | 5 | Get-Module FSLogixMigration -ErrorAction SilentlyContinue | Remove-Module -Force 6 | 7 | Import-Module "$rootPath\..\FSLogixMigration.psd1" -Force 8 | 9 | <# 10 | Fix mock before 11 | Describe -Tag 'Unit', 'Acceptance' "$functionName Unit Test" { 12 | Context "Unit Test Function $functionName" { 13 | It "$functionName has the correct Parameters" { 14 | Get-Command $functionName | Should -HaveParameter ProfilePath -Mandatory 15 | Get-Command $functionName | Should -HaveParameter Target -Mandatory 16 | Get-Command $functionName | Should -HaveParameter VHD -Not -Mandatory 17 | } 18 | 19 | Mock Get-Profile { 20 | [pscustomobject]@{ 21 | ProfilePath = 'C:\Users\jpester' 22 | } 23 | } 24 | 25 | Mock Get-ADUser { 26 | New-Object Microsoft.ActiveDirectory.Management.ADUser Identity -Property @{ 27 | SamAccountName = 'jpester' 28 | SID = 'S-1-5-21-3286950516-3440391731-2706545478-1190' 29 | ObjectGUID = 'a264c192-b541-417a-8ea1-5c13eec4a284' 30 | } 31 | } 32 | 33 | It "$functionName has to gathers profile by ParentPath as parameter and verify UserSID" { 34 | (New-MigrationObject -ProfilePath "C:\Users\jpester" -Target "t:\test\").UserSID | Should -BeExactly "S-1-5-21-3286950516-3440391731-2706545478-1190" 35 | } 36 | 37 | It "$functionName has to gathers profile by ParentPath as parameter and verify UserGUID" { 38 | (New-MigrationObject -ProfilePath "C:\Users\jpester" -Target "t:\test\").UserGUID | Should -BeExactly "a264c192-b541-417a-8ea1-5c13eec4a284" 39 | } 40 | 41 | $profile = Get-ProfileSource -ProfilePath "C:\Users\jpester" | New-MigrationObject -Target "t:\test\" 42 | It "$functionName has to gathers profile by ParentPath as pipeline and verify UserSID" { 43 | $profile.UserSID | Should -BeExactly "S-1-5-21-3286950516-3440391731-2706545478-1190" 44 | } 45 | 46 | It "$functionName has to gathers profile by ParentPath as pipeline and verify UserGUID" { 47 | $profile.UserGUID | Should -BeExactly "a264c192-b541-417a-8ea1-5c13eec4a284" 48 | } 49 | } 50 | } 51 | #> 52 | 53 | Describe -Tag 'Acceptance' "$functionName Acceptance Test" { 54 | Context "Test Function $functionName" { 55 | It "$functionName has the correct Parameters" { 56 | Get-Command $functionName | Should -HaveParameter ProfilePath -Mandatory 57 | Get-Command $functionName | Should -HaveParameter Target -Mandatory 58 | Get-Command $functionName | Should -HaveParameter VHD -Not -Mandatory 59 | } 60 | 61 | $profilesPath = "F:\Roaming Profile$\" 62 | 63 | $profiles = Get-ChildItem -Path $profilesPath 64 | $profilePath = $profiles | select -Last 1 | select -ExpandProperty FullName 65 | 66 | It "$functionName has to gathers profile by ParentPath as parameter" { 67 | (New-MigrationObject -ProfilePath $profilePath -Target "t:\test\").UserSID | Should -Not -BeExactly "SID Not Found" 68 | (New-MigrationObject -ProfilePath $profilePath -Target "t:\test\").UserGUID | Should -Not -BeExactly "GUID Not Found" 69 | } 70 | 71 | It "$functionName has to gathers profile by ParentPath as pipeline" { 72 | $profile = Get-ProfileSource -ParentPath $profilesPath | select -Last 1 | New-MigrationObject -Target "t:\test\" 73 | ($profile).UserSID | Should -Not -BeExactly "SID Not Found" 74 | ($profile).UserGUID | Should -Not -BeExactly "GUID Not Found" 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/Tests/New-ProfileDisk.Test.ps1: -------------------------------------------------------------------------------- 1 | $rootPath = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | 3 | $functionName = ($MyInvocation.MyCommand.Name).Split(".")[0] 4 | 5 | Get-Module FSLogixMigration -ErrorAction SilentlyContinue | Remove-Module -Force 6 | 7 | Import-Module "$rootPath\..\FSLogixMigration.psd1" -Force 8 | 9 | Describe -Tag 'Acceptance' "$functionName Acceptance Test" { 10 | 11 | Context "Test Function $functionName" { 12 | It "$functionName has the correct Parameters" { 13 | Get-Command $functionName | Should -HaveParameter Target -Mandatory 14 | Get-Command $functionName | Should -HaveParameter ProfilePath -Not -Mandatory 15 | Get-Command $functionName | Should -HaveParameter Username -Mandatory 16 | Get-Command $functionName | Should -HaveParameter Size -Mandatory 17 | Get-Command $functionName | Should -HaveParameter SectorSize -Not -Mandatory 18 | } 19 | 20 | It "$functionName has the correct private functions" { 21 | "$rootPath\..\Helper Functions\$functionName.ps1" | Should -FileContentMatch "function New-FSLogixVHD" 22 | "$rootPath\..\Helper Functions\$functionName.ps1" | Should -FileContentMatch "function Mount-FSLogixVHD" 23 | } 24 | 25 | $profilesPath = "F:\Roaming Profile$\" 26 | 27 | $profiles = Get-ChildItem -Path $profilesPath 28 | $OutputObject = @() 29 | $profiles | ForEach-Object { 30 | $profileName = $_.Name 31 | $largeprofile = [math]::Round(((Get-ChildItem "${profilesPath}${profileName}" -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object -Sum length | Select -ExpandProperty Sum) / 1MB), 2) 32 | if ($largeprofile -gt 1) { 33 | $Item = New-Object -TypeName PSObject 34 | $Item | Add-Member -MemberType NoteProperty -Name Name -Value $profileName 35 | $Item | Add-Member -MemberType NoteProperty -Name FullName -Value $_.FullName 36 | $Item | Add-Member -MemberType NoteProperty -Name "Size(MB)" -Value $largeprofile 37 | $OutputObject += $Item 38 | } 39 | } 40 | 41 | $ProfilePath = $OutputObject | Sort-Object 'Size(MB)' -Descending | select -Last 1 42 | $VersionRegex = "(?i)(\.V\d)" 43 | $UserName = ($ProfilePath.Name -split $VersionRegex)[0] 44 | $ProfilePath = $ProfilePath.FullName 45 | $vhdxPath = "$($env:TEMP)\test.vhdx" 46 | 47 | $tempFiles = Get-ChildItem $env:temp | Where-Object { $_.Name -like "test*" } 48 | $tempFiles | Remove-Item -Force -Confirm:$false -Recurse -ErrorAction SilentlyContinue 49 | 50 | It "$functionName has to create profile disk from parameter" { 51 | $testResult = New-ProfileDisk -Target $vhdxPath -ProfilePath $ProfilePath -Username $UserName -Size 15 -SectorSize 512 52 | $testResult.Drive | Should -Not -BeNullOrEmpty 53 | $testResult.Target | Should -BeExactly $vhdxPath 54 | } 55 | Dismount-VHD $vhdxPath -ErrorAction SilentlyContinue 56 | Remove-Item $vhdxPath -Force -Confirm:$false 57 | 58 | $vhdxPath = "$($env:TEMP)\" 59 | $tests = @{ 'VHD' = $true; 60 | 'VHDX' = ""; 61 | } 62 | 63 | foreach ($test in $tests.GetEnumerator()) { 64 | $testName = $test.Name 65 | $testParameter = $test.Value 66 | 67 | if ($testParameter) { 68 | $params = @{ 69 | 'VHD' = $true 70 | } 71 | } 72 | else { 73 | $params = @{ } 74 | } 75 | 76 | It "$functionName has to create profile disk and test if $testName was created from pipeline" { 77 | $batchObject = Get-ProfileSource -ProfilePath $ProfilePath | New-MigrationObject -Target "$($env:TEMP)\" @params 78 | $testResult = New-ProfileDisk -ProfilePath (($batchObject).ProfilePath) -Target (($batchObject).Target) -Username (($batchObject).Username) -Size 15 -SectorSize 512 79 | $testResult.Drive | Should -Not -BeNullOrEmpty 80 | $testResult.Target | Should -Not -BeNullOrEmpty 81 | Dismount-VHD $testResult.Target -ErrorAction SilentlyContinue 82 | Remove-Item $testResult.Target -Force -Confirm:$false 83 | } 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/Tests/New-ProfileReg.Test.ps1: -------------------------------------------------------------------------------- 1 | $rootPath = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | 3 | $functionName = ($MyInvocation.MyCommand.Name).Split(".")[0] 4 | 5 | Get-Module FSLogixMigration -ErrorAction SilentlyContinue | Remove-Module -Force 6 | 7 | Import-Module "$rootPath\..\FSLogixMigration.psd1" -Force 8 | 9 | Describe -Tag 'Acceptance' "$functionName Acceptance Test" { 10 | Context "Test Function $functionName" { 11 | It "$functionName has the correct Parameters" { 12 | Get-Command $functionName | Should -HaveParameter UserSID -Mandatory 13 | Get-Command $functionName | Should -HaveParameter Drive -Mandatory 14 | } 15 | 16 | $UserSID = Get-ADUser Guest -Properties SID | select -ExpandProperty SID | select -ExpandProperty value 17 | 18 | $targetPath = "$($env:TEMP)\" 19 | $regFilePath = "${targetPath}Profile\AppData\local\FSLogix\ProfileData.reg" 20 | 21 | It "$functionName has to create .reg file from parameter and check is not null or empty" { 22 | if (Test-Path -Path $regFilePath) { 23 | Remove-Item $regFilePath -Force -Confirm:$false 24 | } 25 | New-ProfileReg -UserSID $UserSID -Drive $targetPath 26 | Get-Content -Path $regFilePath | Should -Not -BeNullOrEmpty 27 | } 28 | 29 | It "$functionName has to check if .reg file contains correct User SID" { 30 | ( -join (Get-Content -Path $regFilePath)) | Should -BeLike "*$UserSID*" 31 | } 32 | 33 | <# 34 | It "$functionName has to create .reg file from pipeline" { 35 | $vhdxPath = "$($env:TEMP)\test.vhdx" 36 | if (Test-Path -Path $vhdxPath) { 37 | Remove-Item $vhdxPath -Force -Confirm:$false 38 | } 39 | $ProfilePath = Get-ChildItem -Path "C:\Users" -Exclude "Public" | select -Last 1 40 | $UserName = $ProfilePath.Name 41 | $ProfilePath = $ProfilePath.FullName 42 | $testDrive = New-ProfileDisk -Target $vhdxPath -ProfilePath $ProfilePath -Username $UserName -Size 1 43 | if (Test-Path -Path "${testDrive}Profile\AppData\local\FSLogix\ProfileData.reg") { 44 | Remove-Item "${testDrive}Profile\AppData\local\FSLogix\ProfileData.reg" -Force -Confirm:$false 45 | } 46 | $testDrive | New-ProfileReg -UserSID $UserSID 47 | Get-Content -Path "${testDrive}Profile\AppData\local\FSLogix\ProfileData.reg" | Should -Not -BeNullOrEmpty 48 | Get-Content -Path "${testDrive}Profile\AppData\local\FSLogix\ProfileData.reg" | Should -Contain UserSID 49 | } 50 | Dismount-VHD $vhdxPath -ErrorAction SilentlyContinue 51 | if (Test-Path -Path $vhdxPath) { 52 | Remove-Item $vhdxPath -Force -Confirm:$false 53 | } 54 | #> 55 | } 56 | } -------------------------------------------------------------------------------- /FSLogix/FSLogixMigration/Tests/Write-Log.Test.ps1: -------------------------------------------------------------------------------- 1 | $rootPath = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | 3 | $functionName = ($MyInvocation.MyCommand.Name).Split(".")[0] 4 | 5 | Get-Module FSLogixMigration -ErrorAction SilentlyContinue | Remove-Module -Force 6 | 7 | Import-Module "$rootPath\..\FSLogixMigration.psd1" -Force 8 | 9 | Describe -Tag 'Acceptance' "$functionName Acceptance Test" { 10 | Context "Test Function $functionName" { 11 | It "$functionName has the correct Parameters" { 12 | Get-Command $functionName | Should -HaveParameter LogPath -Not -Mandatory 13 | Get-Command $functionName | Should -HaveParameter Message -Mandatory 14 | } 15 | 16 | It "$functionName has to log message as an input string parameter" { 17 | $testPath = "TestDrive:\test.txt" 18 | $testMessage = "Testing-$functionName" 19 | Write-Log -LogPath $testPath -Message $testMessage 20 | $result = Get-Content $testPath 21 | (-join $result) | Should -BeLike "*$testMessage*" 22 | } 23 | 24 | It "$functionName has to log message from pipeline" { 25 | $testPath = "TestDrive:\test.txt" 26 | Get-Service -Name Bits | Select-Object -ExpandProperty Name *>&1 | Write-Log -LogPath $testPath 27 | $result = Get-Content $testPath 28 | (-join $result) | Should -BeLike "*Bits*" 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /FSLogix/Microsoft-StateRepositoryReset.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Resets the StateRepository Database. 4 | .DESCRIPTION 5 | The StateRepository database is used by the StateRepository service which is in turn used by the AppReadiness and AppXsvc services. 6 | This script attempts to stop the services and delete the database files, which will the be recreated automatically. 7 | .INPUTS 8 | None 9 | .NOTES 10 | Version : 1.0 11 | Author : Michael Mardahl 12 | Twitter : @michael_mardahl 13 | Blogging on : www.msendpointmgr.com 14 | Creation Date : August 29th 2021 15 | Purpose/Change: Initial script 16 | License : MIT (Leave author credits) 17 | .EXAMPLE 18 | Execute script as SYSTEM 19 | .\invoke-StateRepositoryReset.ps1 20 | .NOTES 21 | Remember to take backups of your servers before running this thing on a regular basis. 22 | The script outputs a log file to C:\TEMP\last_StateRepositoryResetLog.txt 23 | #> 24 | 25 | #requires -RunAsAdministrator 26 | 27 | Start-Transcript C:\TEMP\last_StateRepositoryResetLog.txt 28 | 29 | Write-Verbose "Resetting StateRepository Databases for AppX / App Readiness" -Verbose 30 | Write-Verbose "Attempting to stop StateRepository service" -Verbose 31 | 32 | $retry = 3 33 | DO{ 34 | Get-Service -Name StateRepository | Stop-Service -Force 35 | Start-Sleep 1 36 | Get-Service -Name AppReadiness | Stop-Service -Force 37 | Start-Sleep 1 38 | $retry-- 39 | } While ( 40 | ((Get-Service -Name StateRepository).Status -ne "Stopped") -and ($retry -gt 0) 41 | ) 42 | 43 | if ((Get-Service -Name StateRepository).Status -eq "Stopped") { 44 | Write-Verbose "StateRepository service is stopped" -Verbose 45 | Write-Verbose "Attempting to delete old StateRepository database files..." -Verbose 46 | del C:\ProgramData\Microsoft\Windows\AppRepository\StateRepository* 47 | Write-Verbose "Done. Starting StateRepository Service" -Verbose 48 | Get-Service -Name StateRepository | Start-Service 49 | } else { 50 | Write-Verbose "StateRepository service failed to stop! Terminating." -Verbose 51 | } 52 | Stop-Transcript 53 | -------------------------------------------------------------------------------- /FSLogix/Office365-MailboxSize.ps1: -------------------------------------------------------------------------------- 1 | # 2 | # Powershell script for estimating Exchange Mailbox Size per user 3 | # 4 | # Generated by: Leee Jeffries 5 | # 6 | # Generated on: 19/06/18 7 | # 8 | #This script will loop through each user and shared mailbox and estimate mailbox cache usage per day and month for the life of the mailbox 9 | #A CSV files is then exported with the details of each mailbox, useful for estimating the size of VHD containers 10 | # 11 | #Remember to set your export location for the CSV 12 | #Export Location - Edit this location 13 | $exportLocation = "C:\temp\Export.csv" 14 | 15 | #Check if a remote powershell session exists, if not then connect one 16 | try { 17 | Get-PSSession -Name ExchangeRM -ErrorAction Stop > $null 18 | } 19 | catch { 20 | #Grab User Credentials 21 | $UserCredential = Get-Credential 22 | 23 | #Set the session parameters to for the Exchange Online Powershell Session 24 | $Session = New-PSSession -Name "ExchangeRM" -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection 25 | 26 | #Import the Powershell Session for Exchange Online 27 | Import-PSSession $Session 28 | } 29 | 30 | #Fill a variable with all mailboxes 31 | $mailboxes = Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox,SharedMailbox 32 | 33 | #Create an array for mailbox objects 34 | $objectArray = @() 35 | 36 | #Loop through each mailbox and grab the name of the mailbox and fill a variable with mailbox items 37 | foreach ($mailbox in $mailboxes) { 38 | $oldestItem = $mailbox | Get-MailboxFolderStatistics | Sort-Object CreationTime | Select-Object CreationTime -First 1 39 | $newestItem = Get-Date 40 | $mailBoxAge = New-TimeSpan -Start $oldestItem.CreationTime -End $newestItem 41 | 42 | $mailboxitems = $mailbox | Get-MailboxFolderStatistics | Select-Object Name,FolderAndSubfolderSize,ItemsInFolderAndSubfolders 43 | $object = New-Object –TypeName PSObject 44 | 45 | #Loop through all mailbox items and calculate sizes 46 | foreach ($mailboxitem in $mailboxitems) { 47 | $object = New-Object –TypeName PSObject 48 | 49 | $totalSizeInMB = $totalSizeInMB + $mailboxitem.FolderAndSubfolderSize.ToString().Split("(")[1].Split(" ")[0].Replace(",","")/1MB 50 | $totalSizeInGB = $totalSizeInGB + $mailboxitem.FolderAndSubfolderSize.ToString().Split("(")[1].Split(" ")[0].Replace(",","")/1GB 51 | $totalItems = $totalItems + $mailboxitem.ItemsInFolderAndSubfolders 52 | } 53 | 54 | #Populate object 55 | $object | Add-Member –MemberType NoteProperty –Name "Mailbox Name" –Value $mailbox.Name -Force 56 | $object | Add-Member –MemberType NoteProperty –Name "Mailbox Size in MB" –Value ([math]::Round($totalSizeInMB,2)) -Force 57 | $object | Add-Member –MemberType NoteProperty –Name "Mailbox Size in GB" –Value ([math]::Round($totalSizeInGB,2)) -Force 58 | $object | Add-Member –MemberType NoteProperty –Name "Mailbox Items" –Value $totalItems -Force 59 | $object | Add-Member –MemberType NoteProperty –Name "Mailbox Age (Days)" –Value $mailBoxAge.Days -Force 60 | $object | Add-Member –MemberType NoteProperty –Name "Cache Per Day MB" –Value ([math]::Round($totalSizeInMB/$mailBoxAge.Days,2)) -Force 61 | $object | Add-Member –MemberType NoteProperty –Name "Cache Per Day GB" –Value ([math]::Round($totalSizeInGB/$mailBoxAge.Days,2)) -Force 62 | $object | Add-Member –MemberType NoteProperty –Name "Cache Per Month MB" –Value ([math]::Round(($totalSizeInMB/$mailBoxAge.Days)*30,2)) -Force 63 | $object | Add-Member –MemberType NoteProperty –Name "Cache Per Month GB" –Value ([math]::Round(($totalSizeInGB/$mailBoxAge.Days)*30,2)) -Force 64 | 65 | #Add to the array 66 | $objectArray += $object 67 | 68 | #Clear variables 69 | $totalSizeinMB = 0 70 | $totalSizeinGB = 0 71 | $totalItems = 0 72 | 73 | "Processed - $mailbox.Name" 74 | } 75 | 76 | #List out array objects 77 | $objectArray 78 | $objectArray | Export-Csv -NoTypeInformation -Path $exportLocation 79 | -------------------------------------------------------------------------------- /FSLogix/README.md: -------------------------------------------------------------------------------- 1 | # FSLogix 2 | 3 | ## FSLogix-ShowMountedVolumes.ps1 4 | Show FSLogix currently mounted volume details & cross reference to FSLogix session information in the registry 5 | 6 | ## FSLogix-ShrinkDisk.ps1 7 | Shrinks FSLogix Profile and O365 dynamically expanding disk(s). 8 | 9 | ## FSLogix-CompactContainer.ps1 10 | This script shrinks/compacts and defragments VHD/VHDX files, reclaiming whitespace within the VHD/VHDX file. 11 | 12 | ## FSLogix-ExpandContainer.ps1 13 | This script expands VHD/VHDX files. It is created specifically for FSLogix Containers. 14 | 15 | ## FSLogix-LocalProfileToProfileContainer.ps1 16 | This script migrate from Local Profile to FSLogix Profile Container 17 | 18 | ## FSLogix-ProfileDiskToProfileContainer.ps1 19 | This script migrate from Profile Disk to FSLogix Profile Container 20 | 21 | ## FSLogix-UPMToProfileContainer.ps1 22 | This script migrate from UPM to FSLogix Profile Container 23 | -------------------------------------------------------------------------------- /FSLogix/targets_ODFC.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ODFC\*.ost 5 | ODFC\*.nst 6 | 7 | -------------------------------------------------------------------------------- /FSLogix/user.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | manuel 4 | user01 5 | -------------------------------------------------------------------------------- /MSIX App Attach/0-PreStage.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | The script create a VHD/X File and copy the MSIX Data in it 4 | .DESCRIPTION 5 | Use this script to create a MSIX App Attach Container 6 | .NOTES 7 | Version: 1.0 8 | Author: Manuel Winkel 9 | Creation Date: 2020-06-04 10 | Purpose/Change: 11 | #> 12 | 13 | #region variables 14 | 15 | $vhdSrc="" 16 | $packageName = "" 17 | $parentFolder = "" 18 | $msixmgrPath = "" 19 | $msixPath = "" 20 | 21 | $parentFolder = "\" + $parentFolder 22 | $parts = $packageName.split("_") 23 | $volumeName = "MSIX-" + $parts[0] 24 | $packagePath = $msixPath + $packageName + ".msix" 25 | #endregion 26 | 27 | #Generate a VHD or VHDX package for MSIX 28 | new-vhd -sizebytes 1024MB -path $vhdSrc -dynamic -confirm:$false 29 | $vhdObject = Mount-VHD $vhdSrc -Passthru 30 | $disk = Initialize-Disk -Passthru -Number $vhdObject.Number 31 | $partition = New-Partition -AssignDriveLetter -UseMaximumSize -DiskNumber $disk.Number 32 | Format-Volume -FileSystem NTFS -Confirm:$false -DriveLetter $partition.DriveLetter -Force 33 | $Path = $partition.DriveLetter + ":" + $parentFolder 34 | 35 | #Create a folder with Package Parent Folder Variable as the name of the folder in root drive mounted above 36 | new-item -path $Path -ItemType Directory 37 | Set-Volume -DriveLetter $partition.DriveLetter -NewFileSystemLabel $volumeName 38 | 39 | 40 | #Expand MSIX in CMD in Admin cmd prompt - Get the full package name 41 | cd $msixmgrPath 42 | .\msixmgr.exe -Unpack -packagePath $packagePath -destination $Path -applyacls 43 | -------------------------------------------------------------------------------- /MSIX App Attach/1-stage.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | The script mount the VHD/X File 4 | .DESCRIPTION 5 | Use this script to mount a MSIX App Attach Container 6 | .NOTES 7 | Version: 1.0 8 | Author: Manuel Winkel 9 | Creation Date: 2020-06-04 10 | Purpose/Change: 11 | #> 12 | #region variables 13 | $vhdSrc="" 14 | $packageName = "" 15 | $parentFolder = "" 16 | $volumeGuid = "" 17 | 18 | $parentFolder = "\" + $parentFolder + "\" 19 | $msixJunction = "C:\temp\AppAttach\" 20 | #endregion 21 | 22 | #region mountvhd 23 | try 24 | { 25 | Mount-Diskimage -ImagePath $vhdSrc -NoDriveLetter -Access ReadOnly 26 | Write-Host ("Mounting of " + $vhdSrc + " was completed!") -BackgroundColor Green 27 | } 28 | catch 29 | { 30 | Write-Host ("Mounting of " + $vhdSrc + " has failed!") -BackgroundColor Red 31 | } 32 | #endregion 33 | 34 | 35 | #region makelink 36 | $msixDest = "\\?\Volume{" + $volumeGuid + "}\" 37 | 38 | if (!(Test-Path $msixJunction)) 39 | { 40 | md $msixJunction 41 | } 42 | 43 | $msixJunction = $msixJunction + $packageName 44 | 45 | cmd.exe /c mklink /j $msixJunction $msixDest 46 | #endregion 47 | 48 | #region stage 49 | [Windows.Management.Deployment.PackageManager,Windows.Management.Deployment,ContentType=WindowsRuntime] | Out-Null 50 | Add-Type -AssemblyName System.Runtime.WindowsRuntime 51 | $asTask = ([System.WindowsRuntimeSystemExtensions].GetMethods() | Where { $_.ToString() -eq 'System.Threading.Tasks.Task`1[TResult] AsTask[TResult,TProgress](Windows.Foundation.IAsyncOperationWithProgress`2[TResult,TProgress])'})[0] 52 | $asTaskAsyncOperation = $asTask.MakeGenericMethod([Windows.Management.Deployment.DeploymentResult], [Windows.Management.Deployment.DeploymentProgress]) 53 | 54 | $packageManager = [Windows.Management.Deployment.PackageManager]::new() 55 | 56 | $path = $msixJunction + $parentFolder + $packageName # needed if we do the pbisigned.vhd 57 | $path = ([System.Uri]$path).AbsoluteUri 58 | 59 | $asyncOperation = $packageManager.StagePackageAsync($path, $null, "StageInPlace") 60 | 61 | $task = $asTaskAsyncOperation.Invoke($null, @($asyncOperation)) 62 | 63 | $task 64 | #endregion 65 | -------------------------------------------------------------------------------- /MSIX App Attach/2-register.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | The script register the MSIX package in user context 4 | .DESCRIPTION 5 | Use this script to register the MSIX App Attach Container in the user context 6 | .NOTES 7 | Version: 1.0 8 | Author: Manuel Winkel 9 | Creation Date: 2020-06-04 10 | Purpose/Change: 11 | #> 12 | #region variables 13 | $packageName = "" 14 | 15 | $path = "C:\Program Files\WindowsApps\" + $packageName + "\AppxManifest.xml" 16 | #endregion 17 | 18 | #region register 19 | Add-AppxPackage -Path $path -DisableDevelopmentMode -Register 20 | #endregion 21 | -------------------------------------------------------------------------------- /MSIX App Attach/3-deregister.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | The script deregister the MSIX Package 4 | .DESCRIPTION 5 | Use this script to deregister the MSIX Package 6 | .NOTES 7 | Version: 1.0 8 | Author: Manuel Winkel 9 | Creation Date: 2020-06-04 10 | Purpose/Change: 11 | #> 12 | #region variables 13 | $packageName = "" 14 | #endregion 15 | 16 | #region derregister 17 | Remove-AppxPackage -PreserveRoamableApplicationData $packageName 18 | #endregion 19 | -------------------------------------------------------------------------------- /MSIX App Attach/4-destage.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | The script unmount the VHD/X File 4 | .DESCRIPTION 5 | Use this script to unmount a MSIX App Attach Container 6 | .NOTES 7 | Version: 1.0 8 | Author: Manuel Winkel 9 | Creation Date: 2020-06-04 10 | Purpose/Change: 11 | #> 12 | #region variables 13 | $packageName = "" 14 | $vhdSrc="" 15 | 16 | $msixJunction = "C:\temp\AppAttach\" 17 | #endregion 18 | 19 | #region derregister 20 | Remove-AppxPackage -AllUsers -Package $packageName 21 | 22 | cd $msixJunction 23 | rmdir $packageName -Recurse -Force -Confirm:$false 24 | #endregion 25 | 26 | #Dismount VHD 27 | disMount-Diskimage -ImagePath $vhdSrc 28 | -------------------------------------------------------------------------------- /MSIX App Attach/README.md: -------------------------------------------------------------------------------- 1 | # MSIX app attach preview 2 | 3 | This repository contains samples to showcase APIs for implementing MSIX app attach. 4 | 5 | Due to this being a preview Microsoft does not offer support for MSIX app attach. All support efforts are community driven. 6 | 7 | Please reference Getting started with MSIX app attach in Windows Virtual Desktop (Preview) document for details on MSIX app attach. 8 | 9 | ### Additional materials 10 | 11 | Please note that the assets below are community supproted and best effort. They do not come with support from Microsoft. 12 | 13 | * [Windows Virtual Desktop on Azure | Released](https://www.youtube.com/watch?v=QLDu6QVohEI) (Microsoft Mechanics, Youtube.com) 14 | * [Windows Virutal Desktop Tech Community](https://techcommunity.microsoft.com/t5/Windows-Virtual-Desktop/bd-p/WindowsVirtualDesktop) 15 | * [MSIX app attach will fundamentally change working with application landscapes on Windows Virtual Desktop!](https://blogs.msdn.microsoft.com/rds/2015/07/13/azure-resource-manager-template-for-rds-deployment) (blog series) [Freek Berson] 16 | * [Create an MSIX package from a desktop installer (MSI, EXE or App-V) on a VM](https://docs.microsoft.com/en-us/windows/msix/packaging-tool/create-app-package-msi-vm) (Docs.Microsoft.com) 17 | * [Automatic MSIX App Attach script for WVD](https://blog.itprocloud.de/Automatic-MSIX-app-attach-scripts/) (blog) [Marcel Meurer] 18 | 19 | ## Get the OS image 20 | - Open the [Windows Insider portal](https://www.microsoft.com/software-download/windowsinsiderpreviewadvanced?wa=wsignin1.0) and sign in. 21 | - Scroll down to the Select edition section and select Windows 10 Insider Preview Enterprise (FAST) – Build 19035 or later. 22 | - Select Confirm, then select the language you wish to use, and then select Confirm again. 23 | - When the download link is generated, select the 64-bit Download and save it to your local hard disk. 24 | 25 | ## Prepare the VHD Image 26 | After you've created your master VHD image, you must disable automatic updates for MSIX app attach applications. To disable automatic updates, you'll need to run the following commands in an elevated command prompt: 27 | ```` 28 | rem Disable Store auto update: 29 | 30 | reg add HKLM\Software\Policies\Microsoft\WindowsStore /v AutoDownload /t REG_DWORD /d 0 /f 31 | Schtasks /Change /Tn "\Microsoft\Windows\WindowsUpdate\Automatic app update" /Disable 32 | Schtasks /Change /Tn "\Microsoft\Windows\WindowsUpdate\Scheduled Start" /Disable 33 | 34 | rem Disable Content Delivery auto download apps that they want to promote to users: 35 | 36 | reg add HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager /v PreInstalledAppsEnabled /t REG_DWORD /d 0 /f 37 | 38 | reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager\Debug /v ContentDeliveryAllowedOverride /t REG_DWORD /d 0x2 /f 39 | 40 | rem Disable Windows Update: 41 | 42 | sc config wuauserv start=disabled 43 | ```` 44 | 45 | ## Generate a VHD or VHDX package for MSIX 46 | Packages are in VHD or VHDX format to optimize performance. MSIX requires VHD or VHDX packages to work properly. 47 | To generate a VHD or VHDX package for MSIX: 48 | 49 | - Download the [msixmgr tool](https://aka.ms/msixmgr) and save the .zip folder to a folder within a session host VM. 50 | - Unzip the msixmgr tool .zip folder. 51 | - Put the source MSIX package into the same folder where you unzipped the msixmgr tool. 52 | - Run the following cmdlet in PowerShell to create a VHD: 53 | ```` 54 | New-VHD -SizeBytes MB -Path c:\temp\.vhd -Dynamic -Confirm:$false 55 | ```` 56 | - Run the following cmdlet to mount the newly created VHD: 57 | ```` 58 | $vhdObject = Mount-VHD c:\temp\.vhd -Passthru 59 | ```` 60 | - Run this cmdlet to initialize the VHD: 61 | ```` 62 | $disk = Initialize-Disk -Passthru -Number $vhdObject.Number 63 | ```` 64 | - Run this cmdlet to create a new partition: 65 | ```` 66 | $partition = New-Partition -AssignDriveLetter -UseMaximumSize -DiskNumber $disk.Number 67 | ```` 68 | - Run this cmdlet to format the partition: 69 | ```` 70 | Format-Volume -FileSystem NTFS -Confirm:$false -DriveLetter $partition.DriveLetter -Force 71 | ```` 72 | - Create a parent folder on the mounted VHD. This step is mandatory as the MSIX app attach requires a parent folder. You can name the parent folder whatever you like. 73 | ### Expand MSIX 74 | After that, you'll need to "expand" the MSIX image by unpacking it. To unpack the MSIX image: 75 | - Open a command prompt as Administrator and navigate to the folder where you downloaded and unzipped the msixmgr tool. 76 | - Run the following cmdlet to unpack the MSIX into the VHD you created and mounted in the previous section. 77 | ```` 78 | msixmgr.exe -Unpack -packagePath .msix -destination "f:\" -applyacls 79 | ```` 80 | The following message should appear once unpacking is done: 81 | ```` 82 | Successfully unpacked and applied ACLs for package: .msix 83 | ```` 84 | - Navigate to the mounted VHD and open the app folder and confirm package content is present. 85 | - Unmount the VHD. 86 | 87 | ## Install certificates 88 | If your app uses a certificate that isn't public-trusted or was self-signed, here's how to install it: 89 | 90 | - Right-click the package and select Properties. 91 | - In the window that appears, select the Digital signatures tab. There should be only one item in the list on the tab, as shown in the following image. Select that item to highlight the item, then select Details. 92 | - When the digital signature details window appears, select the General tab, then select Install certificate. 93 | - When the installer opens, select local machine as your storage location, then select Next. 94 | - If the installer asks you if you want to allow the app to make changes to your device, select Yes. 95 | - Select Place all certificates in the following store, then select Browse. 96 | - When the select certificate store window appears, select Trusted people, then select OK. 97 | - Select Finish. 98 | 99 | ## Prepare PowerShell scripts for MSIX app attach 100 | MSIX app attach has four distinct phases that must be performed in the following order: 101 | 102 | 1. Stage 103 | 2. Register 104 | 3. Deregister 105 | 4. Destage 106 | 107 | Each phase creates a PowerShell script. Sample scripts for each phase are available here. 108 | 109 | After you've disabled automatic updates, you must enable Hyper-V because you'll be using the Mound-VHD command to stage and and Dismount-VHD to destage. 110 | -------------------------------------------------------------------------------- /OneDrive/OneDrive-CopyFromFolderRedirectionToOneDrive.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | The script copies the existing folders "desktop" and "documents" into the onedrive directory 4 | 5 | .DESCRIPTION 6 | Use this script to copy the Folder Redirection Folder Desktop and Documents local and fetch them into OneDrive Known Folder 7 | 8 | .NOTES 9 | Version: 1.0 10 | Author: Manuel Winkel 11 | Creation Date: 2020-03-04 12 | Purpose/Change: 13 | #> 14 | 15 | $Documents = [environment]::getfolderpath("mydocuments") 16 | $Desktop = [environment]::getfolderpath("desktop") 17 | $Target = $env:OneDriveCommercial 18 | 19 | write-host 'OneDrive connected and found' 20 | robocopy $Documents $Target"/Documents" /E /SEC 21 | robocopy $Desktop $Target"/Desktop" /E /SEC 22 | new-item $Documents -name '_FILES COPIED TO ONEDRIVE.txt' -ItemType 'file' -Value 'Files Copied' -force 23 | new-item $Desktop -name '_FILES COPIED TO ONEDRIVE.txt' -ItemType 'file' -Value 'Files Copied' -force 24 | -------------------------------------------------------------------------------- /OneDrive/OneDrive-SetFilesOnDemandToCloudOnly.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | The script sets all files in OneDrive path to Cloud Only 4 | 5 | .DESCRIPTION 6 | Use this script to free space on your local system 7 | 8 | .NOTES 9 | Version: 1.0 10 | Author: Manuel Winkel 11 | Creation Date: 2023-08-22 12 | Purpose/Change: 13 | #> 14 | 15 | 16 | get-childitem $ENV:OneDriveCommercial -Force -File -Recurse -ErrorAction SilentlyContinue | 17 | Where-Object {$_.Attributes -match 'ReparsePoint' -or $_.Attributes -eq '525344' } | 18 | ForEach-Object { 19 | attrib.exe $_.fullname +U -P /s 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microsoft 2 | ## About FSLogix-RemoveContainerData.ps1 3 | FSLogix-RemoveContainerData is used to delete files and folders in a user's FSLogix Profile and Office Container by mounting the Container and pruning files, thus keeping the Container size to a minimum. The script reads an XML file that defines a list of files and folders to remove from the Container. Actions on a target path can be: 4 | 5 | Prune - the XML can include a number that defines the age in days for last write that the file must be older than to be deleted. Essentially reducing the size of the folder. 6 | Delete - the target path will be deleted. Where the administrator may want to remove a target path, the Delete action will delete the entire folder. 7 | Trim - where the target path contains sub-folders, this action will remove all sub-folders except for the newest folder. 8 | Supports -WhatIf and -Verbose output and returns a list of files removed from the profile. Add -Verbose will output the total size of files removed from the user profile and processing time at the end of the script. All targets (files / folders) that are deleted, will be logged to a file. 9 | 10 | Deleting files from the Container can potentially result in data loss, so testing is advised and the use of -Confirm:$false is required for the script perform a delete. 11 | 12 | This script depends on the following PowerShell modules: 13 | 14 | ActiveDirectory - Installed as a feature in Windows Server or via RSAT 15 | Hyper-V - Installed as a feature in Windows Server or via RSAT 16 | Fslogix.Powershell.Disk - this module is found here: https://github.com/aaronparker/fslogix/tree/main/Modules/Fslogix.Powershell.Disk 17 | 18 | ### Usage 19 | 20 | .\FSLogix-RemoveContainerData.ps1 -Path \\server\FSLogixContainer -Targets .\targets.xml -Type Profile 21 | 22 | #### Running FSLogix-RemoveContainerData.ps1 23 | FSLogix-RemoveContainerData.ps1 must be run outside the user session when Profile Containers are not in use. The script will require exclusive access to the Container to mount it with read/write access. Remove-ContainerData.ps1 could be run as a schedule task outside of business hours from a management host. 24 | -------------------------------------------------------------------------------- /SQL/README.md: -------------------------------------------------------------------------------- 1 | # Microsoft SQL 2 | 3 | ## Microsoft-SQLAccountExpiry.ps1 4 | Find AD accounts with passwords or accounts expiring within the specified number of days or are locked out or disabled and optionally send an email containing the information 5 | -------------------------------------------------------------------------------- /Teams/README.md: -------------------------------------------------------------------------------- 1 | # Teams 2 | PowerShell Scripts 3 | 4 | 5 | Teams_Autostart_Reset_CU.ps1 -> Script must be executed once in user context 6 | 7 | Teams_Autostart_Reset_LM.ps1 -> Script must be executed once in machine context (Adminstrative PowerShell) 8 | -------------------------------------------------------------------------------- /Teams/Teams-AutostartResetCU.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script allows you to delete all autostart settings for Teams from the user context. 4 | 5 | .DESCRIPTION 6 | If you want to use the "Prevent Microsoft Teams from starting automatically after installation" 7 | Group Policy setting, make sure you first set the Group Policy setting to the value you want 8 | before you run this script. 9 | 10 | Script must be executed once in user context. 11 | You must first use the other Script for the Machine Context (Teams_Autostart_Reset_LM.ps1) 12 | 13 | .NOTES 14 | Version: 2.0 15 | Author: Manuel Winkel 16 | Creation Date: 2020-03-04 17 | Purpose/Change: Edit for Version 1.2.00.32462 and newer 18 | #> 19 | 20 | $ErrorActionPreference = "Stop" 21 | 22 | $TeamsDesktopConfigJsonPath = [System.IO.Path]::Combine($env:APPDATA, 'Microsoft', 'Teams', 'desktop-config.json') 23 | 24 | $TeamsUpdatePath = [System.IO.Path]::Combine($env:LOCALAPPDATA, 'Microsoft', 'Teams', 'Update.exe') 25 | 26 | Function Test-RegistryValue { 27 | param( 28 | [Alias("PSPath")] 29 | [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 30 | [String]$Path 31 | , 32 | [Parameter(Position = 1, Mandatory = $true)] 33 | [String]$Name 34 | ) 35 | 36 | process { 37 | if (Test-Path $Path) { 38 | $Key = Get-Item -LiteralPath $Path 39 | if ($null -ne $Key.GetValue($Name, $null)) { 40 | $true 41 | } else { 42 | $false 43 | } 44 | } else { 45 | $false 46 | } 47 | } 48 | } 49 | 50 | Function Test-Remove-RegistryValue { 51 | param ( 52 | [Alias("PSPath")] 53 | [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 54 | [String]$Path 55 | , 56 | [Parameter(Position = 1, Mandatory = $true)] 57 | [String]$Name 58 | ) 59 | 60 | process { 61 | if (Test-RegistryValue -Path $Path -Name $Name) { 62 | Write-Host "Removing registry key $Path\$Name" 63 | Remove-ItemProperty -Path $Path -Name $Name 64 | } 65 | } 66 | } 67 | 68 | # when determining whether Teams should be auto-started we are checking three flags 69 | Write-Host "Removing Auto-Start-related artifacts" 70 | 71 | # 0. Close Teams, if running 72 | $teamsProc = Get-Process -name Teams -ErrorAction SilentlyContinue 73 | if ($null -ne $teamsProc) { 74 | Write-Host "Stopping Microsoft Teams..." 75 | Stop-Process -Name Teams -Force 76 | # wait some time 77 | Start-Sleep 5 78 | } else { 79 | Write-Host "No running Teams process found" 80 | } 81 | 82 | # 1. Check that Teams process isn't still running 83 | $teamsProc = Get-Process -name Teams -ErrorAction SilentlyContinue 84 | 85 | if($null -eq $teamsProc) { 86 | # 2. remove HKEY_CURRENT_USER\Software\Microsoft\Office\Teams\LoggedInOnce registry key 87 | Test-Remove-RegistryValue -Path "HKCU:\Software\Microsoft\Office\Teams" -Name "LoggedInOnce" 88 | 89 | # 3. remove HKEY_CURRENT_USER\Software\Microsoft\Office\Teams\HomeUserUpn registry key 90 | Test-Remove-RegistryValue -Path "HKCU:\Software\Microsoft\Office\Teams" -Name "HomeUserUpn" 91 | 92 | # 4. remove HKEY_CURRENT_USER\Software\Microsoft\Office\Teams\DeadEnd registry key 93 | Test-Remove-RegistryValue -Path "HKCU:\Software\Microsoft\Office\Teams" -Name "DeadEnd" 94 | 95 | # 5. remove HKCU:\Software\Microsoft\Office\Outlook\Addins\TeamsAddin.FastConnect registry key 96 | Remove-Item -Path "HKCU:\Software\Microsoft\Office\Outlook\Addins\TeamsAddin.FastConnect" -ErrorAction SilentlyContinue 97 | 98 | # 6. remove HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run\com.squirrel.Teams.Teams 99 | Test-Remove-RegistryValue -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "com.squirrel.Teams.Teams" 100 | 101 | # 7. We are checking whether there are entries 'isLoggedOut' and 'openAtLogin' in the desktop-config.json file 102 | if (Test-Path -Path $TeamsDesktopConfigJsonPath) { 103 | Write-Host "Changing entries 'guestTenantId', 'isLoggedOut' and 'openAtLogin' in the desktop-config.json, if exist" 104 | 105 | # open desktop-config.json file 106 | $desktopConfigFile = Get-Content -path $TeamsDesktopConfigJsonPath -Raw | ConvertFrom-Json 107 | $desktopConfigFile.PSObject.Properties.Remove("guestTenantId") 108 | $desktopConfigFile.PSObject.Properties.Remove("isLoggedOut") 109 | try { 110 | $desktopConfigFile.appPreferenceSettings.openAtLogin = $true 111 | } catch { 112 | Write-Host "openAtLogin JSON element doesn't exist" 113 | } 114 | $desktopConfigFile | ConvertTo-Json -Compress | Set-Content -Path $TeamsDesktopConfigJsonPath -Force 115 | } 116 | } else { 117 | Write-Host "Teams process is still running, aborting script execution" 118 | } 119 | -------------------------------------------------------------------------------- /Teams/Teams-AutostartResetLM.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script allows you to delete all autostart settings for the machine. 4 | 5 | .DESCRIPTION 6 | If you want to use the "Prevent Microsoft Teams from starting automatically after installation" 7 | Group Policy setting, make sure you first set the Group Policy setting to the value you want 8 | before you run this script. 9 | 10 | Script must be executed once in machine context (Administrative PowerShell) 11 | You must first use this Script and then the User Context (Teams_Autostart_Reset_CU.ps1) script 12 | 13 | .NOTES 14 | Version: 2.0 15 | Author: Manuel Winkel 16 | Creation Date: 2020-03-04 17 | Purpose/Change: Edit for Version 1.2.00.32462 and newer 18 | #> 19 | 20 | $ErrorActionPreference = "Stop" 21 | 22 | $TeamsDesktopConfigJsonPath = [System.IO.Path]::Combine($env:APPDATA, 'Microsoft', 'Teams', 'desktop-config.json') 23 | 24 | $TeamsUpdatePath = [System.IO.Path]::Combine($env:LOCALAPPDATA, 'Microsoft', 'Teams', 'Update.exe') 25 | 26 | Function Test-RegistryValue { 27 | param( 28 | [Alias("PSPath")] 29 | [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 30 | [String]$Path 31 | , 32 | [Parameter(Position = 1, Mandatory = $true)] 33 | [String]$Name 34 | ) 35 | 36 | process { 37 | if (Test-Path $Path) { 38 | $Key = Get-Item -LiteralPath $Path 39 | if ($null -ne $Key.GetValue($Name, $null)) { 40 | $true 41 | } else { 42 | $false 43 | } 44 | } else { 45 | $false 46 | } 47 | } 48 | } 49 | 50 | Function Test-Remove-RegistryValue { 51 | param ( 52 | [Alias("PSPath")] 53 | [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 54 | [String]$Path 55 | , 56 | [Parameter(Position = 1, Mandatory = $true)] 57 | [String]$Name 58 | ) 59 | 60 | process { 61 | if (Test-RegistryValue -Path $Path -Name $Name) { 62 | Write-Host "Removing registry key $Path\$Name" 63 | Remove-ItemProperty -Path $Path -Name $Name 64 | } 65 | } 66 | } 67 | 68 | # when determining whether Teams should be auto-started we are checking three flags 69 | Write-Host "Removing Auto-Start-related artifacts" 70 | 71 | # 0. Close Teams, if running 72 | $teamsProc = Get-Process -name Teams -ErrorAction SilentlyContinue 73 | if ($null -ne $teamsProc) { 74 | Write-Host "Stopping Microsoft Teams..." 75 | Stop-Process -Name Teams -Force 76 | # wait some time 77 | Start-Sleep 5 78 | } else { 79 | Write-Host "No running Teams process found" 80 | } 81 | 82 | # 1. Check that Teams process isn't still running 83 | $teamsProc = Get-Process -name Teams -ErrorAction SilentlyContinue 84 | 85 | if($null -eq $teamsProc) { 86 | # 2. remove HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run\Teams registry key 87 | Test-Remove-RegistryValue -Path "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Run" -Name "Teams" 88 | 89 | # 3. We are checking whether there are entries 'isLoggedOut' and 'openAtLogin' in the desktop-config.json file 90 | if (Test-Path -Path $TeamsDesktopConfigJsonPath) { 91 | Write-Host "Changing entries 'guestTenantId', 'isLoggedOut' and 'openAtLogin' in the desktop-config.json, if exist" 92 | 93 | # open desktop-config.json file 94 | $desktopConfigFile = Get-Content -path $TeamsDesktopConfigJsonPath -Raw | ConvertFrom-Json 95 | $desktopConfigFile.PSObject.Properties.Remove("guestTenantId") 96 | $desktopConfigFile.PSObject.Properties.Remove("isLoggedOut") 97 | try { 98 | $desktopConfigFile.appPreferenceSettings.openAtLogin = $true 99 | } catch { 100 | Write-Host "openAtLogin JSON element doesn't exist" 101 | } 102 | $desktopConfigFile | ConvertTo-Json -Compress | Set-Content -Path $TeamsDesktopConfigJsonPath -Force 103 | } 104 | } else { 105 | Write-Host "Teams process is still running, aborting script execution" 106 | } 107 | -------------------------------------------------------------------------------- /Teams/Teams-ClearCache.ps1: -------------------------------------------------------------------------------- 1 | 2 | <# 3 | .SYNOPSIS 4 | This script clears the local teams 5 | 6 | .DESCRIPTION 7 | Clears the local Teams Cache 8 | 9 | .NOTES 10 | Version: 1.0 11 | Author: Manuel Winkel 12 | Creation Date: 2022-01-30 13 | Purpose/Change: 14 | 15 | .EXAMPLE 16 | WEM: 17 | Path: powershell.exe 18 | Arguments: -executionpolicy bypass -file Teams-ClearCache.ps1" 19 | #> 20 | 21 | Write-Host "Microsoft Teams will be quit now in order to clear the cache." 22 | try{ 23 | Get-Process -ProcessName Teams | Stop-Process -Force 24 | Start-Sleep -Seconds 5 25 | Write-Host "Microsoft Teams has been successfully quit." 26 | } 27 | catch{ 28 | echo $_ 29 | } 30 | # The cache is now being cleared. 31 | try{ 32 | Get-ChildItem -Path $env:APPDATA\"Microsoft\teams\code cache\cache" | Remove-Item 33 | Get-ChildItem -Path $env:APPDATA\"Microsoft\teams\blob_storage" | Remove-Item 34 | Get-ChildItem -Path $env:APPDATA\"Microsoft\teams\databases" | Remove-Item 35 | Get-ChildItem -Path $env:APPDATA\"Microsoft\teams\cache" | Remove-Item 36 | Get-ChildItem -Path $env:APPDATA\"Microsoft\teams\gpucache" | Remove-Item 37 | Get-ChildItem -Path $env:APPDATA\"Microsoft\teams\Indexeddb" | Remove-Item 38 | Get-ChildItem -Path $env:APPDATA\"Microsoft\teams\Local Storage" | Remove-Item 39 | Get-ChildItem -Path $env:APPDATA\"Microsoft\teams\tmp" | Remove-Item 40 | 41 | } 42 | catch{ 43 | echo $_ 44 | } 45 | 46 | write-host "The Microsoft Teams cache has been successfully cleared." 47 | -------------------------------------------------------------------------------- /Teams/Teams-FindVulnerable.ps1: -------------------------------------------------------------------------------- 1 | # https://www.theregister.com/2020/12/07/microsoft_teams_rce_flaw/ 2 | # https://github.com/oskarsve/ms-teams-rce 3 | # Taking the vulnerable version from the above repo. I'm hoping that's the latest version this flaw exists on 4 | # Output format is for limitations in our MSP software 5 | # username:version (shows what version user is running, for every user running Teams at time of scan) 6 | # RunMin = Lowest version found running 7 | # RunMax = Highest version found running 8 | # InstalledVersions = What versions are registered in add/remove programs 9 | # and if the installed version is vulnerable, but user versions have updated, let us know situation is actually OK 10 | 11 | $vulnerable = New-Object System.Version 1.3.00.21759 12 | $script:runningMax = New-Object System.Version 0.0.0.0 13 | $script:runningMin = New-Object System.Version 10.0.0.0 14 | 15 | Function comp ($v) { 16 | if ($v -gt $script:runningMax) { $script:runningMax = $v} 17 | if ($v -lt $script:runningMin) { $script:runningMin = $v} 18 | } 19 | 20 | $isVulnerable = $false 21 | $BadVersions = @() 22 | $Procs = Get-WmiObject Win32_Process -Filter "name='teams.exe'" | Select ProcessID, Name, @{Name="UserName";Expression={$_.GetOwner().Domain+"\"+$_.GetOwner().User}} 23 | # this test shouldn't be needed, but PS2 is screwing it up... 24 | if ($null -ne $procs) { 25 | ForEach ($Proc in $procs) { 26 | $process = Get-Process -id $proc.processid 27 | $v = New-Object System.Version $process.productversion 28 | comp $v 29 | if ($v -le $vulnerable) { 30 | $isVulnerable = $true 31 | $BadVersions += "$($Proc.UserName):$($Process.ProductVersion)" 32 | } 33 | } 34 | $BadVersions = @($BadVersions | Sort -Unique) 35 | $BadVersions += "RunMin $($script:runningMin) RunMax $($script:runningMax)" 36 | if ($script:runningMin -le $vulnerable) { 37 | $BadVersions += "Running vulnerable version" 38 | } else { 39 | $BadVersions += "Oldest running is OK tho" 40 | } 41 | } 42 | 43 | $SW = @(Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* -ea SilentlyContinue | Where-Object { $_.DisplayName -match 'Teams' -and $_.Publisher -match 'Microsoft Corporation' -and $_.SystemComponent -ne 0x1 -and $_.ParentDisplayName -eq $null } | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate, HelpLink, UninstallString) 44 | $SW += @(Get-ItemProperty HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* -ea SilentlyContinue | Where-Object { $_.DisplayName -match 'Teams' -and $_.Publisher -match 'Microsoft Corporation' -and $_.SystemComponent -ne 0x1 -and $_.ParentDisplayName -eq $null } | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate, HelpLink, UninstallString) 45 | ForEach ($TeamsInstallation in $SW) { 46 | # just in case somehow >1 here 47 | $InstalledVersion = New-Object System.Version $TeamsInstallation.DisplayVersion 48 | if ($InstalledVersion -le $vulnerable) { 49 | $isVulnerable = $true 50 | $BadVersions += "InstalledVuln $($InstalledVersion)" 51 | } 52 | } 53 | if ($isVulnerable) { 54 | $BadVersions -join "," 55 | } 56 | -------------------------------------------------------------------------------- /Teams/Teams-LogoffUserSettings.ps1: -------------------------------------------------------------------------------- 1 | # Hide powershell prompt 2 | Add-Type -Name win -MemberDefinition '[DllImport("user32.dll")] public static extern bool ShowWindow(int handle, int state);' -Namespace native 3 | [native.win]::ShowWindow(([System.Diagnostics.Process]::GetCurrentProcess() | Get-Process).MainWindowHandle,0) 4 | 5 | $ErrorActionPreference = 'SilentlyContinue' 6 | 7 | # Optimize Microsoft Teams 8 | $JsonFile = [System.IO.Path]::Combine($env:AppData, 'Microsoft', 'Teams', 'desktop-config.json') 9 | 10 | If (Test-Path -Path $JsonFile) { 11 | $ConfigFile = Get-Content -Path $JsonFile -Raw | ConvertFrom-Json 12 | Get-Process -Name Teams | Stop-Process -Force 13 | $ConfigFile.appPreferenceSettings.disableGpu = $True 14 | $ConfigFile.appPreferenceSettings.openAtLogin = $True 15 | $ConfigFile.appPreferenceSettings.openAsHidden = $True 16 | $ConfigFile.appPreferenceSettings.runningOnClose = $True 17 | $ConfigFile.appPreferenceSettings.registerAsIMProvider = $True 18 | $ConfigFile | ConvertTo-Json -Compress | Set-Content -Path $JsonFile -Force 19 | } Else { 20 | Write-Host "JSON file doesn't exist" 21 | } -------------------------------------------------------------------------------- /Teams/Teams-MachineBasedCleanUp.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script allows you to uninstall the Microsoft Teams app and remove Teams directory for a user. 4 | 5 | .DESCRIPTION 6 | Use this script to clear the installed Microsoft Teams application. Run this PowerShell script for each user profile for which the Teams App was installed on a machine. After the PowerShell has executed on all user profiles, Teams can be redeployed. 7 | 8 | .NOTES 9 | Version: 2.0 10 | Author: Manuel Winkel 11 | Creation Date: 2020-03-04 12 | Purpose/Change: Edit for Version 1.2.00.32462 and newer 13 | #> 14 | 15 | 16 | # Hide powershell prompt 17 | Add-Type -Name win -MemberDefinition '[DllImport("user32.dll")] public static extern bool ShowWindow(int handle, int state);' -Namespace native 18 | [native.win]::ShowWindow(([System.Diagnostics.Process]::GetCurrentProcess() | Get-Process).MainWindowHandle,0) 19 | 20 | $TeamsUpdateExePathMachine = [System.IO.Path]::Combine('c:\Program Files (x86)', 'Microsoft', 'Teams', 'Update.exe') 21 | $TeamsExePath = [System.IO.Path]::Combine($env:LOCALAPPDATA, 'Microsoft', 'Teams') 22 | $TeamsExePathMachine = [System.IO.Path]::Combine('c:\Program Files (x86)', 'Microsoft', 'Teams') 23 | $TeamsAppData = [System.IO.Path]::Combine($env:APPDATA, 'Microsoft', 'Teams') 24 | $TeamsAppData2 = [System.IO.Path]::Combine($env:APPDATA, 'Microsoft Teams') 25 | $TeamsLocalAppData = [System.IO.Path]::Combine($env:LOCALAPPDATA, 'Microsoft', 'Teams') 26 | $SquirrelTemp = [System.IO.Path]::Combine($env:LOCALAPPDATA, 'SquirrelTemp ') 27 | $TeamsStartMenuShortcut = “c:\users\$env:USERNAME.$env:USERDOMAIN\Start Menu\Programs\Microsoft Corporation” 28 | $TeamsDesktopShortcut = “c:\users\$env:USERNAME.$env:USERDOMAIN\Desktop\Microsoft Teams.lnk" 29 | $TeamsPresenceAddinPathMachine = [System.IO.Path]::Combine('c:\Program Files (x86)', 'Microsoft', 'TeamsPresenceAddin') 30 | 31 | 32 | try 33 | { 34 | If (Test-Path -Path $TeamsExePathMachine) { 35 | Write-Host "Uninstalling Microsoft Teams Machine Based..." 36 | 37 | # Kill teams.exe 38 | If (Get-Process -Name Teams -ErrorAction SilentlyContinue) { 39 | Stop-Process -Name Teams -Force 40 | } 41 | 42 | #If (Test-Path -Path $TeamsUpdateExePathMachine) { 43 | #Write-Host "Uninstalling Microsoft Teams Machine Based..." 44 | # Uninstall app 45 | #$proc = Start-Process -FilePath $TeamsUpdateExePathMachine -ArgumentList "-uninstall -s" -PassThru -ErrorAction SilentlyContinue 46 | #$proc.WaitForExit() 47 | #} 48 | 49 | If (Test-Path -Path $TeamsExePathMachine) { 50 | Write-Host "Deleting Microsoft Teams Machine Based Install directory..." 51 | Get-ChildItem $TeamsExePathMachine -Recurse | Remove-Item 52 | Remove-Item $TeamsExePathMachine -Recurse -Force 53 | } 54 | 55 | # Delete Microsoft Teams AppData directory 56 | If (Test-Path -Path $TeamsAppData) { 57 | Write-Host "Deleting Microsoft/Teams AppData directory..." 58 | Get-ChildItem $TeamsAppData -Recurse | Remove-Item -Force 59 | } 60 | 61 | If (Test-Path -Path $TeamsAppData2) { 62 | Write-Host "Deleting Microsoft Teams AppData directory..." 63 | Get-ChildItem $TeamsAppData2 -Recurse | Remove-Item -Force 64 | } 65 | 66 | # Delete Microsoft Teams LocalAppData directory 67 | If (Test-Path -Path $TeamsLocalAppData) { 68 | Write-Host "Deleting Microsoft Teams LocalAppData directory..." 69 | Get-ChildItem $TeamsLocalAppData -Recurse | Remove-Item -Force 70 | } 71 | 72 | # Delete Microsoft Teams SquirrelTemp directory 73 | If (Test-Path -Path $SquirrelTemp) { 74 | Write-Host "Deleting Microsoft Teams SquirrelTemp directory..." 75 | Get-ChildItem $SquirrelTemp -Recurse | Remove-Item -Force 76 | } 77 | 78 | # Delete Microsoft Teams PresenceAddin directory 79 | If (Test-Path -Path $TeamsPresenceAddinPathMachine) { 80 | Write-Host "Deleting Microsoft Teams PresenceAddin directory..." 81 | Get-ChildItem $TeamsPresenceAddinPathMachine -Recurse | Remove-Item -Force 82 | Remove-Item $TeamsPresenceAddinPathMachine -Recurse -Force 83 | } 84 | 85 | # Delete Microsoft Teams start menu shortcut 86 | If (Test-Path -Path $TeamsStartMenuShortcut) { 87 | Write-Host "Deleting Microsoft Teams start menu shortcut..." 88 | Remove-Item -Path $TeamsStartMenuShortcut -Recurse 89 | } 90 | 91 | # Delete Microsoft Teams desktop shortcut 92 | If (Test-Path -Path $TeamsDesktopShortcut) { 93 | Write-Host "Deleting Microsoft Teams desktop shortcut" 94 | Remove-Item -Path $TeamsDesktopShortcut -Recurse 95 | } 96 | 97 | 98 | } 99 | } 100 | catch 101 | { 102 | Write-Error -ErrorRecord $_ 103 | Exit 1 104 | } 105 | -------------------------------------------------------------------------------- /Teams/Teams-UserBasedCleanUp.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script allows you to uninstall the Microsoft Teams app and remove Teams directory for a user. 4 | 5 | .DESCRIPTION 6 | Use this script to clear the installed Microsoft Teams application. Run this PowerShell script for each user profile for which the Teams App was installed on a machine. After the PowerShell has executed on all user profiles, Teams can be redeployed. 7 | 8 | .NOTES 9 | Version: 2.0 10 | Author: Manuel Winkel 11 | Creation Date: 2020-03-04 12 | Purpose/Change: Edit for Version 1.2.00.32462 and newer 13 | #> 14 | 15 | 16 | # Hide powershell prompt 17 | Add-Type -Name win -MemberDefinition '[DllImport("user32.dll")] public static extern bool ShowWindow(int handle, int state);' -Namespace native 18 | [native.win]::ShowWindow(([System.Diagnostics.Process]::GetCurrentProcess() | Get-Process).MainWindowHandle,0) 19 | 20 | $TeamsUpdateExePath = [System.IO.Path]::Combine($env:LOCALAPPDATA, 'Microsoft', 'Teams', 'Update.exe') 21 | $TeamsAppData = [System.IO.Path]::Combine($env:APPDATA, 'Microsoft', 'Teams') 22 | $TeamsAppData2 = [System.IO.Path]::Combine($env:APPDATA, 'Microsoft Teams') 23 | $TeamsLocalAppData = [System.IO.Path]::Combine($env:LOCALAPPDATA, 'Microsoft', 'Teams') 24 | $SquirrelTemp = [System.IO.Path]::Combine($env:LOCALAPPDATA, 'SquirrelTemp ') 25 | $TeamsStartMenuShortcut = “c:\users\$env:USERNAME.$env:USERDOMAIN\Start Menu\Programs\Microsoft Corporation” 26 | $TeamsDesktopShortcut = “c:\users\$env:USERNAME.$env:USERDOMAIN\Desktop\Microsoft Teams.lnk" 27 | 28 | 29 | try 30 | { 31 | If (Test-Path -Path $TeamsUpdateExePath) { 32 | Write-Host "Uninstalling Microsoft Teams..." 33 | 34 | # Kill teams.exe 35 | If (Get-Process -Name Teams -ErrorAction SilentlyContinue) { 36 | Stop-Process -Name Teams -Force 37 | } 38 | 39 | # Uninstall app 40 | $proc = Start-Process -FilePath $TeamsUpdateExePath -ArgumentList "-uninstall -s" -PassThru -ErrorAction SilentlyContinue 41 | $proc.WaitForExit() 42 | 43 | # Delete Microsoft Teams AppData directory 44 | If (Test-Path -Path $TeamsAppData) { 45 | Write-Host "Deleting Microsoft Teams AppData directory..." 46 | Remove-Item -Path $TeamsAppData -Recurse 47 | } 48 | 49 | If (Test-Path -Path $TeamsAppData2) { 50 | Write-Host "Deleting Microsoft Teams AppData directory..." 51 | Remove-Item -Path $TeamsAppData2 -Recurse 52 | } 53 | 54 | # Delete Microsoft Teams LocalAppData directory 55 | If (Test-Path -Path $TeamsLocalAppData) { 56 | Write-Host "Deleting Microsoft Teams LocalAppData directory..." 57 | Remove-Item -Path $TeamsLocalAppData -Recurse 58 | } 59 | 60 | # Delete Microsoft Teams SquirrelTemp directory 61 | If (Test-Path -Path $SquirrelTemp) { 62 | Write-Host "Deleting Microsoft Teams SquirrelTemp directory..." 63 | Remove-Item -Path $SquirrelTemp -Recurse 64 | } 65 | 66 | # Delete Microsoft Teams start menu shortcut 67 | If (Test-Path -Path $TeamsStartMenuShortcut) { 68 | Write-Host "Deleting Microsoft Teams start menu shortcut..." 69 | Remove-Item -Path $TeamsStartMenuShortcut -Recurse 70 | } 71 | 72 | # Delete Microsoft Teams desktop shortcut 73 | If (Test-Path -Path $TeamsDesktopShortcut) { 74 | Write-Host "Deleting Microsoft Teams desktop shortcut" 75 | Remove-Item -Path $TeamsDesktopShortcut -Recurse 76 | } 77 | 78 | } 79 | 80 | } 81 | catch 82 | { 83 | Write-Error -ErrorRecord $_ 84 | Exit 1 85 | } 86 | -------------------------------------------------------------------------------- /Teams/Teams-UserSettings.ps1: -------------------------------------------------------------------------------- 1 | 2 | <# 3 | .SYNOPSIS 4 | This script allows you to define the Teams settings per user 5 | 6 | .DESCRIPTION 7 | Define the Teams settings. 8 | 9 | .NOTES 10 | Version: 1.3 11 | Author: Manuel Winkel 12 | Creation Date: 2020-11-30 13 | Purpose/Change: Added more Teams settings 14 | 31.05.2021 Matthias Schlimm/EUCweb: test if desktop-config.json exists and create it with default values or updating the existing one. Added Powershell Transcript 15 | 16 | .EXAMPLE 17 | WEM: 18 | Path: powershell.exe 19 | Arguments: -executionpolicy bypass -file Teams-UserSettings.ps1" 20 | #> 21 | 22 | #Define settings 23 | param( 24 | #Enable or disable GPU acceleration 25 | [boolean]$disableGpu=$True, 26 | #Fully close Teams App 27 | [boolean]$runningOnClose=$true, 28 | #Auto-start application 29 | [boolean]$openAtLogin=$False, 30 | #Register Teams as the default chat app for office 31 | [boolean]$registerAsIMProvider=$False, 32 | #Open Teams hidden 33 | [boolean]$openAsHidden=$true 34 | ) 35 | 36 | 37 | $ConfigFile = "$ENV:APPDATA\Microsoft\Teams\desktop-config.json" 38 | start-transcript $env:appdata\Set-Teams-UserSettings.log 39 | Write-Host "--- Teams Settings --- " 40 | Write-Host "disableGpu=$disableGpu" 41 | Write-Host "runningOnClose=$runningOnClose" 42 | Write-Host "openAtLogin=$openAtLogin" 43 | Write-Host "registerAsIMProvider=$registerAsIMProvider" 44 | Write-Host "openAsHidden=$openAsHidden" 45 | 46 | #Read Teams Configuration from desktop-config.json 47 | IF (Test-path $ConfigFile ) { 48 | Write-Host "Get Content from File $ConfigFile" 49 | #Convert file to PowerShell object from desktop-config.json 50 | $JSONObject=Convertfrom-Json -inputobject (Get-Content $ConfigFile -Raw) 51 | 52 | Write-Host "Update Teams Settings" 53 | #Update settings from desktop-config.json 54 | $JSONObject.appPreferenceSettings.disableGpu=$disableGpu 55 | $JSONObject.appPreferenceSettings.runningOnClose=$runningOnClose 56 | $JSONObject.appPreferenceSettings.openAtLogin=$openAtLogin 57 | $JSONObject.appPreferenceSettings.registerAsIMProvider=$registerAsIMProvider 58 | $JSONObject.appPreferenceSettings.openAsHidden=$openAsHidden 59 | 60 | #Rewrite configuration to file from desktop-config.json 61 | $NewFileContent=$JSONObject | ConvertTo-Json | Set-Content -Path $ConfigFile 62 | 63 | } Else { 64 | $TeamsPath = [System.IO.Path]::GetDirectoryName($ConfigFile) 65 | IF (!(Test-Path $TeamsPath)) { 66 | Write-Host "Path $TeamsPath doesn't exist, creating Directory" 67 | New-Item -ItemType Directory -Path $TeamsPath 68 | } 69 | Write-Host "File $ConfigFile doesn't exist, creating it with default settings" 70 | $jsonBase = @{} 71 | $JsonDefaultData = @{} 72 | 73 | $JsonDefaultData = @{ 74 | "disableGpu"=$disableGpu; 75 | "runningOnClose"=$runningOnClose; 76 | "openAtLogin"=$openAtLogin; 77 | "registerAsIMProvider"=$registerAsIMProvider; 78 | "openAsHidden"=$openAsHidden; 79 | } 80 | 81 | $jsonBase.Add("appPreferenceSettings",$JsonDefaultData) 82 | $jsonBase.Add("theme","Default") 83 | $jsonBase | ConvertTo-Json -Depth 10 | Out-File $ConfigFile -encoding ASCII 84 | } 85 | 86 | Stop-Transcript 87 | 88 | -------------------------------------------------------------------------------- /Windows/MCT/auto.cmd: -------------------------------------------------------------------------------- 1 | @title Auto Upgrade without prompts + change edition support & (set args=%1) & color 1f& echo off 2 | 3 | set OPTIONS1=/SelfHost /Auto Upgrade /MigChoice Upgrade /DynamicUpdate Enable /UpdateMedia Decline 4 | set OPTIONS2=/Compat IgnoreWarning /MigrateDrivers All /ResizeRecoveryPartition Disable /ShowOOBE None 5 | set OPTIONS3=/Telemetry Disable /CompactOS Disable /SkipSummary /Eula Accept 6 | 7 | if defined args (pushd %args%) else pushd "%~dp0" 8 | for %%i in ("x86\" "x64\" "") do if exist "%%~isources\setupprep.exe" set "dir=%%~i" 9 | pushd "%dir%sources" || (echo "%dir%sources" & timeout /t 5 & exit/b) 10 | 11 | ::# elevate so that workarounds can be set 12 | fltmc>nul || (set _="%~f0" %*& powershell -nop -c start -verb runas cmd \"/d/x/rcall $env:_\"& exit/b) 13 | 14 | ::# No 11 Setup Checks on Dynamic Update 15 | call :no_11_setup_checks_on_dynamic_update 16 | 17 | ::# current version query 18 | set NT="HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" 19 | for /f "tokens=2*" %%R in ('reg query %NT% /v EditionID /reg:64 2^>nul') do set "EditionID=%%S" 20 | for /f "tokens=2*" %%R in ('reg query %NT% /v ProductName /reg:64 2^>nul') do set "ProductName=%%S" 21 | for /f "tokens=2*" %%R in ('reg query %NT% /v CurrentBuildNumber /reg:64 2^>nul') do set "Build=%%S" 22 | 23 | ::# media selection from PID.txt - get it verbosely in case auto.cmd is reused without MediaCreationTool.bat 24 | set Value=& set Edition=& if exist PID.txt for /f "delims=" %%v in (PID.txt) do (set %%v)2>nul 25 | if defined Value for %%K in ( 26 | Cloud.V3WVW-N2PV2-CGWC3-34QGF-VMJ2C CloudN.NH9J3-68WK7-6FB93-4K3DF-DJ4F6 27 | Core.YTMG3-N6DKC-DKB77-7M9GH-8HVX7 CoreN.4CPRK-NM3K3-X6XXQ-RXX86-WXCHW 28 | CoreSingleLanguage.BT79Q-G7N6G-PGBYW-4YWX6-6F4BT CoreCountrySpecific.N2434-X9D7W-8PF6X-8DV9T-8TYMD 29 | Professional.VK7JG-NPHTM-C97JM-9MPGT-3V66T ProfessionalN.2B87N-8KFHP-DKV6R-Y2C8J-PKCKT 30 | ProfessionalEducation.8PTT6-RNW4C-6V7J2-C2D3X-MHBPB ProfessionalEducationN.GJTYN-HDMQY-FRR76-HVGC7-QPF8P 31 | ProfessionalWorkstation.DXG7C-N36C4-C4HTG-X4T3X-2YV77 ProfessionalWorkstationN.WYPNQ-8C467-V2W6J-TX4WX-WT2RQ 32 | Education.YNMGQ-8RYV3-4PGQ3-C8XTP-7CFBY EducationN.84NGF-MHBT6-FXBX8-QWJK7-DRR8H 33 | Enterprise.NPPR9-FWDCX-D2C8J-H872K-2YT43 EnterpriseN.DPH2V-TTNVB-4X9Q3-TJR4H-KHJW4 34 | ) do if /i %%~xK equ .%Value% (set Edition=%%~nK) 35 | if defined Edition if /i "%Edition%" neq "%EditionID%" call :rename %Edition% & goto setup force edition if selected 36 | 37 | ::# auto upgrade with edition lie workaround to keep files and apps - all 1904x builds allow up/downgrade between them 38 | if not exist ei.cfg (set vol=0) else (set vol=1) 39 | if /i "Embedded" == "%EditionID%" if %vol% == 1 (call :rename Enterprise) else (call :rename Professional) 40 | if /i "IoTEnterpriseS" == "%EditionID%" if %vol% == 1 (call :rename Enterprise) else (call :rename Professional) 41 | if /i "EnterpriseS" == "%EditionID%" if %vol% == 1 (call :rename Enterprise) else (call :rename Professional) 42 | if /i "EnterpriseSN" == "%EditionID%" if %vol% == 1 (call :rename EnterpriseN) else (call :rename ProfessionalN) 43 | if /i "IoTEnterprise" == "%EditionID%" if %vol% == 0 (call :rename Professional) 44 | if /i "Enterprise" == "%EditionID%" if %vol% == 0 (call :rename Professional) 45 | if /i "EnterpriseN" == "%EditionID%" if %vol% == 0 (call :rename ProfessionalN) 46 | if /i "CoreCountrySpecific" == "%EditionID%" if %vol% == 1 (call :rename Professional) 47 | if /i "CoreSingleLanguage" == "%EditionID%" if %vol% == 1 (call :rename Professional) 48 | if /i "Core" == "%EditionID%" if %vol% == 1 (call :rename Professional) 49 | if /i "CoreN" == "%EditionID%" if %vol% == 1 (call :rename ProfessionalN) 50 | if /i "Ultimate" == "%EditionID%" (call :rename Professional) 51 | if /i "" == "%EditionID%" (call :rename Professional) 52 | 53 | :setup 54 | start "auto" setupprep.exe %OPTIONS1% %OPTIONS2% %OPTIONS3% 55 | exit/b 56 | 57 | :rename EditionID 58 | set NT="HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" 59 | (reg query %NT% /v ProductName_undo /reg:32 || reg add %NT% /v ProductName_undo /d "%ProductName%" /f /reg:32 60 | reg query %NT% /v ProductName_undo /reg:64 || reg add %NT% /v ProductName_undo /d "%ProductName%" /f /reg:64 61 | reg query %NT% /v EditionID_undo /reg:32 || reg add %NT% /v EditionID_undo /d "%EditionID%" /f /reg:32 62 | reg query %NT% /v EditionID_undo /reg:64 || reg add %NT% /v EditionID_undo /d "%EditionID%" /f /reg:64 63 | reg delete %NT% /v ProductName /f /reg:32 & reg add %NT% /v EditionID /d "%~1" /f /reg:32 64 | reg delete %NT% /v ProductName /f /reg:64 & reg add %NT% /v EditionID /d "%~1" /f /reg:64 65 | ) >nul 2>nul &exit/b 66 | 67 | :no_11_setup_checks_on_dynamic_update - also available as standalone toggle script in the MCT folder 68 | set "0=%~f0"& powershell -nop -c "iex ([io.file]::ReadAllText($env:0)-split'#[:]skip[:]tpm.*')[1];" &exit/b #:skip:tpm 69 | $3 = @(); $I = gi -force 'setupprep.exe' -ea 0; if ($I.VersionInfo.FileBuildPart -lt 22000) {return} #:: abort if not 11 media 70 | $3+= ''' Skip TPM Check on Dynamic Update (c) AveYo 2021 : v3 IFEO-based with no flashing cmd window' 71 | $3+= 'C = "cmd /q AveYo /d/x/r erase /f/s/q %systemdrive%\$windows.~bt\appraiserres.dll&"' 72 | $3+= 'M = "md 11&cd 11&ren vd.exe vdsldr.exe &robocopy ""../"" ""./"" ""vdsldr.exe""&ren vdsldr.exe vd.exe&"' 73 | $3+= 'D = "start vd.exe -Embedding" : CreateObject("WScript.Shell").Run C & M & D, 0, False' 74 | $V = "wscript 11.vbs //B //T:5"; $S = [environment]::SystemDirectory 75 | $K = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\vdsldr.exe' 76 | ni $K -force -ea 0 >''; sp $K 'Debugger' $V -force -ea 0; [io.file]::WriteAllText("$S\11.vbs", $3-join"`r`n") 77 | sp 'HKLM:\SYSTEM\Setup\MoSetup' 'AllowUpgradesWithUnsupportedTPMOrCPU' 1 -type dword -force -ea 0 >'' 78 | #:: #:skip:tpm 79 | -------------------------------------------------------------------------------- /Windows/MCT/latest_MCT_script.url: -------------------------------------------------------------------------------- 1 | [InternetShortcut] 2 | URL=https://git.io/MediaCreationTool.bat 3 | -------------------------------------------------------------------------------- /Windows/MCT/no_11_setup_checks_on_dynamic_update.cmd: -------------------------------------------------------------------------------- 1 | @(set "0=%~f0"^)#) & powershell -nop -c iex([io.file]::ReadAllText($env:0)) & exit/b 2 | #:: double-click to run or just copy-paste into powershell - it's a standalone hybrid script 3 | #:: v3 of the toggle script hides the flashing cmd window via vbs 4 | #:: uses IFEO to attach to Virtual Disk Service Loader process running during setup, then erases appraiserres.dll 5 | #:: it must also do some ping-pong renaming of vdsldr in system32\11 6 | #:: you probably don't need to have it installed at all times - just when doing feature updates or manual setup within windows 7 | #:: hence the on off toggle just by running the script again 8 | 9 | $_Paste_in_Powershell = { 10 | $N = "Skip TPM Check on Dynamic Update"; $3 = @("' $N (c) AveYo 2021 : v3 IFEO-based with no flashing cmd window") 11 | $3+= 'C = "cmd /q AveYo /d/x/r erase /f/s/q %systemdrive%\$windows.~bt\appraiserres.dll&"' 12 | $3+= 'M = "md 11&cd 11&ren vd.exe vdsldr.exe &robocopy ""../"" ""./"" ""vdsldr.exe""&ren vdsldr.exe vd.exe&"' 13 | $3+= 'D = "start vd.exe -Embedding" : CreateObject("WScript.Shell").Run C & M & D, 0, False' 14 | $U = 'root\subscription'; $C = gwmi -Class CommandLineEventConsumer -Namespace $U -Filter "Name='$N'" -ea 0 15 | $B = gwmi -Class __FilterToConsumerBinding -Namespace $U -Filter "Filter = ""__eventfilter.name='$N'""" -ea 0 16 | $F = gwmi -Class __EventFilter -NameSpace $U -Filter "Name='$N'" -ea 0; $B,$C,$F |% {$_|rwmi -ea 0} # undo v1 17 | sp 'HKLM:\SYSTEM\Setup\MoSetup' 'AllowUpgradesWithUnsupportedTPMOrCPU' 1 -type dword -force -ea 0 >'' 18 | $K = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\vdsldr.exe' 19 | $V = "wscript 11.vbs //B //T:5"; $S = [environment]::SystemDirectory; if (test-path $K) { 20 | ri $K -force -ea 0 >''; del "$S\11.vbs" -force -ea 0; rmdir "$S\11" -force -re -ea 0 21 | write-host -fore 0xf -back 0xd "`n $N v3 [REMOVED] run again to install " } else { 22 | ni $K -force -ea 0 >''; sp $K Debugger $V -force -ea 0; [io.file]::WriteAllText("$S\11.vbs", $3-join"`r`n") 23 | write-host -fore 0xf -back 0x2 "`n $N v3 [INSTALLED] run again to remove " } ; timeout /t 5 24 | } ; start -verb runas powershell -args "-nop -c & {`n`n$($_Paste_in_Powershell-replace'"','\"')}" 25 | $_Press_Enter 26 | #:: 27 | -------------------------------------------------------------------------------- /Windows/Microsoft-DeleteOldFiles.ps1: -------------------------------------------------------------------------------- 1 | <# Delete all Files in a folder older than X day(s) 2 | 3 | @Deyda 4 | 5 | #> 6 | 7 | <# 8 | .SYNOPSIS 9 | 10 | Delete all Files in a folder older than X day(s) based on parameters 11 | 12 | .PARAMETER path 13 | 14 | Path to the folder, where the files older than x day(s) are deleted 15 | 16 | .PARAMETER days 17 | 18 | Number of days that the files to be deleted are old. Default is 30 days 19 | 20 | 21 | .EXAMPLE 22 | 23 | '.\Microsoft-DeleteOldFiles.ps1' -path "C:\temp" -days -15 24 | 25 | #> 26 | 27 | [CmdletBinding()] 28 | 29 | Param 30 | ( 31 | [string]$path , 32 | [int]$days = -30 33 | ) 34 | 35 | $CurrentDate = Get-Date 36 | $DatetoDelete = $CurrentDate.AddDays($days) 37 | 38 | Get-ChildItem $Path -Recurse | Where-Object { $_.LastWriteTime -lt $DatetoDelete } | Remove-Item -------------------------------------------------------------------------------- /Windows/Microsoft-DeleteProfileForGroupMember.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | Delete profiles for members of an AD group which aren't in use 5 | 6 | .PARAMETER group 7 | 8 | The domain qualified group name for which any unloaded profiles found will be deleted 9 | 10 | .EXAMPLE 11 | 12 | & '.\Microsoft-DeleteProfileForGroupMember.ps1' -group 'guyrleech\Delete Profiles' 13 | 14 | Delete all local user profiles, if the "are you sure" prompt is confirmed, which are unloaded where the profile is for a member of the "Delete Profiles" AD group in domain "guyrleech" 15 | 16 | .EXAMPLE 17 | 18 | & '.\Microsoft-DeleteProfileForGroupMember.ps1' -group 'guyrleech\Delete Profiles' -confirm:$false 19 | 20 | Delete all local user profiles, without prompting for confirmation, which are unloaded where the profile is for a member of the "Delete Profiles" AD group in domain "guyrleech" 21 | 22 | .NOTES 23 | 24 | USE THIS SCRIPT AT YOUR OWN RISK. THE AUTHOR ACCEPTS NO RESPONSIBILITY FOR ANY UNINTENDED LOSS OR DAMAGE CAUSED BY USING THIS SCRIPT. 25 | 26 | Modification History: 27 | 28 | @guyrleech 21/05/20 Initial release 29 | #> 30 | 31 | [CmdletBinding(SupportsShouldProcess,ConfirmImpact='High')] 32 | 33 | Param 34 | ( 35 | [Parameter(Mandatory,HelpMessage='Domain qualified group name whose members will have profiles deleted')] 36 | [string]$group 37 | ) 38 | 39 | ## check group is domain\groupname format 40 | [string[]]$groupparts = $group -split '\\' 41 | if( ! $groupparts -or $groupparts.Count -ne 2 ) 42 | { 43 | Throw "Group `"$group`" not in domain\group format" 44 | } 45 | 46 | $domain = $groupparts[0] 47 | $group = $groupparts[1] 48 | 49 | ## Check group exists 50 | 51 | if( ! ( $adGroup = ([ADSISearcher]"Name=$group").FindOne() ) ) 52 | { 53 | Throw "Unable to find group `"$domain\$group`"" 54 | } 55 | 56 | [int]$totalUnloadedProfiles = 0 57 | 58 | ## if you use Get-CimInstance, there isn't a delete method 59 | [array]$profilesDeleted = @( Get-WmiObject -ClassName Win32_UserProfile -Filter "Special = 'FALSE' and Loaded = 'FALSE'"| ForEach-Object ` 60 | { 61 | $totalUnloadedProfiles++ 62 | $profile = $_ 63 | [string[]]$usernameParts = ([System.Security.Principal.SecurityIdentifier]( $profile.sid )).Translate([System.Security.Principal.NTAccount]).Value -split '\\' 64 | if( $usernameParts -and $usernameParts.Count -eq 2 -and $usernameParts[0] -eq $domain ) 65 | { 66 | Write-Verbose -Message "Checking group membership of $($usernameParts -join '\')" 67 | 68 | if( ( $searcher = [adsisearcher]"(samaccountname=$($usernameParts[1]))" ) ` 69 | -and $searcher.FindOne().Properties.memberof -match "CN=$group,OU=" ) 70 | { 71 | Write-Verbose -Message "Deleting profile in `"$($profile.LocalPath)`" for $($usernameParts -join '\\'), last used $(Get-Date -Date (([WMI] '').ConvertToDateTime($profile.LastUseTime)) -Format G), status $($profile.Status)" 72 | if( $PSCmdlet.ShouldProcess( "In `"$($profile.LocalPath)`" for $($usernameParts -join '\\')" , 'Delete Profile' ) ) 73 | { 74 | $profile.psbase.Delete() 75 | } 76 | Add-Member -InputObject $profile -MemberType NoteProperty -Name Username -Value ($usernameParts -join '\\') -PassThru 77 | } 78 | } 79 | }) 80 | 81 | if( ! $profilesDeleted -or ! $profilesDeleted.Count ) 82 | { 83 | Write-Warning -Message "No unloaded profiles found for group `"$group`" out of $totalUnloadedProfiles profiles checked" 84 | } 85 | else 86 | { 87 | Write-Verbose -Message "Deleted $($profilesDeleted.Count) profile for $($profilesDeleted.username -join ' ')" 88 | } 89 | -------------------------------------------------------------------------------- /Windows/Microsoft-GroupMembershipModifier.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | Use text file with list of machines or list of machines to add or remove specified users in the local group specified (assumes user running has the rights to do this) 4 | 5 | @guyrleech (c) 2018 6 | 7 | Modification history: 8 | 9 | 22/10/18 GRL Added support for local accounts 10 | #> 11 | 12 | <# 13 | .SYNOPSIS 14 | 15 | Add or remove accounts from groups on Windows machines. 16 | 17 | .PARAMETER machines 18 | 19 | A comma separated list of machines names to make the group changes on 20 | 21 | .PARAMETER machinesFile 22 | 23 | A text file containing one machine per line 24 | 25 | .PARAMETER users 26 | 27 | A comma separated list of user names add or remove from the group specified 28 | 29 | .PARAMETER usersFile 30 | 31 | A text file containing one user name per line 32 | 33 | .PARAMETER group 34 | 35 | The name of the group to make the changes to 36 | 37 | .PARAMETER domain 38 | 39 | The domain containing the user accounts. User names prefixed with domain\ will override the domain specified 40 | 41 | .PARAMETER remove 42 | 43 | Remove the specified users from the group specified rather than adding them 44 | 45 | .EXAMPLE 46 | 47 | & '.\Microsoft-GroupMembershipModifier.ps1' -machines machine01,machines02 -users user1,user2 -group "Remote Desktop Users" 48 | 49 | Add the specified users to the specified group on the specified machines 50 | 51 | .EXAMPLE 52 | 53 | & '.\Microsoft-GroupMembershipModifier.ps1' -machinesFile c:\temp\machines.txt -usersFile c:\temp\users.txt -remove 54 | 55 | Remove the users specified one per line in the file c:\temp\users.txt from the "Administrators" group on the machines specified one per line in c:\temp\machines.txt 56 | 57 | .NOTES 58 | 59 | The user running the script must have the rights to perform the group changes otherwise they will fail 60 | #> 61 | 62 | [CmdletBinding()] 63 | 64 | Param 65 | ( 66 | [string[]]$machines = @( 'localhost' ) , 67 | [string]$machinesFile , 68 | [string[]]$users , 69 | [string]$usersFile , 70 | [string]$group = 'Administrators' , 71 | [string]$domain = $env:USERDOMAIN , 72 | [switch]$remove 73 | ) 74 | 75 | if( ! [string]::IsNullOrEmpty( $machinesFile ) ) 76 | { 77 | $machines += Get-Content $machinesFile -ErrorAction Stop 78 | } 79 | 80 | if( ! [string]::IsNullOrEmpty( $usersFile ) ) 81 | { 82 | $users += Get-Content $usersFile -ErrorAction Stop 83 | } 84 | 85 | if( ! $machines -or ! $machines.Count ) 86 | { 87 | Throw "No machines specified to operate on" 88 | } 89 | 90 | [int]$missingUsers = 0 91 | 92 | [array]$adUsers = 93 | @( ForEach( $user in $users ) 94 | { 95 | if( $user -match '^[\- _a-z0-9\.]' ) 96 | { 97 | [string]$domainName,[string]$userName = $user.Trim() -split '\\' 98 | 99 | if( [string]::IsNullOrEmpty( $userName ) ) 100 | { 101 | $userName = $domainName 102 | $domainName = $domain 103 | } 104 | elseif( $domainName -eq '.' ) 105 | { 106 | $domainName = $env:COMPUTERNAME 107 | } 108 | $thisUser = [ADSI]"WinNT://$domainName/$userName,user" 109 | if( ! $thisUser.Path ) 110 | { 111 | Write-Error "Failed to find user $domainName\$userName" 112 | $missingUsers++ 113 | } 114 | else 115 | { 116 | $thisUser 117 | } 118 | } 119 | else 120 | { 121 | Write-Warning "Unexpected format for user `"$user`" - either use domain\ , .\ or just the user name" 122 | } 123 | }) 124 | 125 | if( $missingUsers ) 126 | { 127 | Write-Error "Failed to find $missingUsers user(s) - aborting" 128 | Exit 2 129 | } 130 | 131 | if( ! $adUsers.Count ) 132 | { 133 | Write-Error "No users specified - use -users or -usersFile - aborting" 134 | Exit 3 135 | } 136 | 137 | [string]$verb = $null 138 | [string]$preposition = $null 139 | 140 | if( $remove ) 141 | { 142 | $verb = 'Removing' 143 | $preposition = 'from' 144 | } 145 | else 146 | { 147 | $verb = 'Adding' 148 | $preposition = 'to' 149 | } 150 | 151 | [int]$errors = 0 152 | 153 | $machines | ForEach-Object ` 154 | { 155 | $computerName = $_.Trim() 156 | if( $computerName -match '^[a-z0-9]' ) 157 | { 158 | $localGroup = [ADSI]"WinNT://$computerName/$group,group" 159 | ForEach( $adUser in $adUsers ) 160 | { 161 | [string]$operation = "$verb $((($aduser.Path -split ':')[1] -split ',')[0] -replace '//' , '' -replace '/' , '\') $preposition `"$group`" on $computerName" 162 | Write-Verbose $operation 163 | try 164 | { 165 | if( $remove ) 166 | { 167 | $localGroup.Remove( $adUser.Path ) 168 | } 169 | else 170 | { 171 | $localGroup.Add( $adUser.Path ) 172 | } 173 | } 174 | catch 175 | { 176 | Write-Error "Error $($operation.Substring(0,1).ToLower() + $operation.Substring(1))) - $($_.Exception.Message)" 177 | $errors++ 178 | } 179 | } 180 | } 181 | } 182 | 183 | Write-Verbose "Finished with $errors errors" 184 | 185 | Exit $errors 186 | -------------------------------------------------------------------------------- /Windows/Microsoft-KillProcess.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script kills a process, if this reaches a specific limit and write a log file 4 | 5 | .NOTES 6 | Version: 1.0 7 | Author: Manuel Winkel 8 | Creation Date: 2020-07-16 9 | Purpose/Change: 10 | #> 11 | 12 | cls 13 | $procname = '' 14 | $cpu_usage= 15 | $logfile = 'c:\temp\1.log' 16 | $proc = Get-Process $procname | select cpu,id 17 | "{0} {1} cpu usage: {2}" -f (Get-Date), $procname, $proc.cpu | Out-File -Append $logfile 18 | 19 | if($proc.cpu -gt $cpu_usage){ 20 | "killing process" | Out-File -Append $logfile 21 | kill $proc.id 22 | } 23 | -------------------------------------------------------------------------------- /Windows/Microsoft-LogonFTA.ps1: -------------------------------------------------------------------------------- 1 | # Hide powershell prompt 2 | Add-Type -Name win -MemberDefinition '[DllImport("user32.dll")] public static extern bool ShowWindow(int handle, int state);' -Namespace native 3 | [native.win]::ShowWindow(([System.Diagnostics.Process]::GetCurrentProcess() | Get-Process).MainWindowHandle,0) 4 | 5 | # Set File Associations for Adobe Reader or Adobe Acrobat - https://kolbi.cz/blog/2017/10/25/setuserfta-userchoice-hash-defeated-set-file-type-associations-per-user 6 | Import-Module ActiveDirectory 7 | $User = "$env:UserName" 8 | $AD_Group_AdobeAcrobat = "App-AdobeAcrobat" 9 | 10 | If ((Get-ADUser $User -Properties memberof).memberof -like "CN=$AD_Group_AdobeAcrobat*") 11 | { 12 | .\SetUserFTA.exe "Adobe Acrobat.txt" 13 | } 14 | Else 15 | { 16 | .\SetUserFTA.exe "Adobe Acrobat Reader.txt" 17 | } -------------------------------------------------------------------------------- /Windows/Microsoft-LogonFTA/Adobe Acrobat Reader.txt: -------------------------------------------------------------------------------- 1 | .pdf, AcroExch.Document.DC 2 | .pdfxml, AcroExch.pdfxml 3 | .acrobatsecuritysettings, AcroExch.acrobatsecuritysettings 4 | .fdf, AcroExch.FDFDoc 5 | .xfdf, AcroExch.XFDFDoc 6 | .xdp, AcroExch.XDPDoc 7 | .pdx, PDXFileType 8 | .api, AcroExch.Plugin 9 | .secstore, AcroExch.SecStore -------------------------------------------------------------------------------- /Windows/Microsoft-LogonFTA/Adobe Acrobat.txt: -------------------------------------------------------------------------------- 1 | .pdf, Acrobat.Document.DC 2 | .pdfxml, Acrobat.pdfxml 3 | .acrobatsecuritysettings, Acrobat.acrobatsecuritysettings 4 | .fdf, Acrobat.FDFDoc 5 | .xfdf, Acrobat.XFDFDoc 6 | .xdp, Acrobat.XDPDoc 7 | .pdx, PDXFileType 8 | .api, Acrobat.Plugin 9 | .secstore, Acrobat.SecStore 10 | .sequ, Acrobat.Sequence 11 | .rmf, Acrobat.RMFFile 12 | .bpdx, AcrobatBPDXFileType -------------------------------------------------------------------------------- /Windows/Microsoft-MissingUpdates.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Checks Remote System for the Update State 4 | 5 | .DESCRIPTION 6 | ### 7 | 8 | .PARAMETER Computer 9 | Remote Computer to be checked 10 | 11 | .PARAMETER List 12 | List of Remote Computer to be checked (not yet implemented) 13 | 14 | .PARAMETER Export 15 | Export to path with the computer as name 16 | 17 | .PARAMETER PassThru 18 | Returns an object representing the item with which you are working. By default, this cmdlet does not generate any pipeline output. 19 | 20 | .INPUTS 21 | You can pipe the computer into the command which is recognised by type, you can also pipe any parameter by name. It will also take the path positionally 22 | 23 | .OUTPUTS 24 | This script outputs a csv file with the result of the processing. It will optionally produce a custom object with the same information 25 | 26 | .EXAMPLE 27 | Microsoft-MissingUpdates.ps1 -Computer ADC01 28 | This checks a single a single computer and displays the result in the powershell window 29 | 30 | .EXAMPLE 31 | Microsoft-MissingUpdates.ps1 -Computer ADC01 -Export c:\ 32 | This checks a single a single computer and and export the result to C:\ADC01.txt 33 | 34 | #> 35 | 36 | [CmdletBinding()] 37 | 38 | Param ( 39 | 40 | [Parameter( 41 | Position = 1, 42 | ValuefromPipelineByPropertyName = $true, 43 | ValuefromPipeline = $true, 44 | Mandatory = $true 45 | )] 46 | [System.String]$Computer, 47 | 48 | [Parameter( 49 | ValuefromPipelineByPropertyName = $true 50 | )] 51 | [switch]$PassThru, 52 | 53 | [Parameter( 54 | ValuefromPipelineByPropertyName = $true 55 | )] 56 | [System.String]$Export = "$env:TEMP\Microsoft-MissingUpdate $(Get-Date -Format yyyy-MM-dd` HH-mm-ss).csv" 57 | 58 | ) 59 | 60 | 61 | 62 | Set-StrictMode -Version Latest 63 | #Requires -RunAsAdministrator 64 | 65 | 66 | 67 | $updatesession = [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session","$Computer")) 68 | $UpdateSearcher = $updatesession.CreateUpdateSearcher() 69 | $searchresult = $updatesearcher.Search("IsInstalled=0") # 0 = NotInstalled | 1 = Installed 70 | #$searchresult.Updates.Count 71 | 72 | $Updates = If ($searchresult.Updates.Count -gt 0) { 73 | #Updates are waiting to be installed 74 | $count = $searchresult.Updates.Count 75 | Write-Output "Found $Count update\s on $computer!" 76 | #Cache the count to make the For loop run faster 77 | For ($i=0; $i -lt $Count; $i++) { 78 | #Create object holding update 79 | $Update = $searchresult.Updates.Item($i) 80 | [pscustomobject]@{ 81 | Title = $Update.Title 82 | KB = $($Update.KBArticleIDs) 83 | SecurityBulletin = $($Update.SecurityBulletinIDs) 84 | MsrcSeverity = $Update.MsrcSeverity 85 | IsDownloaded = $Update.IsDownloaded 86 | Description = $Update.Description 87 | RebootRequired = $Update.RebootRequired 88 | ReleaseDate = $Update.LastDeploymentChangeTime 89 | Categories = ($Update.Categories | Select-Object -ExpandProperty Name) 90 | BundledUpdates = @($Update.BundledUpdates)|ForEach{ 91 | [pscustomobject]@{ 92 | Title = $_.Title 93 | } 94 | } 95 | } 96 | } 97 | } 98 | If ($searchresult.Updates.Count -gt 0) { 99 | if ($PassThru) { 100 | $updates |ft kb,title,msrcseverity,ReleaseDate,IsDownloaded,RebootRequired -autosize 101 | } 102 | #$Export = $Export.TrimEnd(".txt") 103 | $Export = $Export.Substring(0, $Export.IndexOf('.')) 104 | $Export = "$Export $Computer.txt" 105 | $updatelist = $updates |ft kb,title,msrcseverity,ReleaseDate,IsDownloaded,RebootRequired 106 | Out-File -FilePath $Export -Width 256 -InputObject $Updatelist -Force 107 | } -------------------------------------------------------------------------------- /Windows/Microsoft-PendingReboot.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Checks Remote Status of Pending Reboot 4 | 5 | .DESCRIPTION 6 | ### 7 | 8 | .PARAMETER Computer 9 | Remote Computer to be checked 10 | 11 | .PARAMETER List 12 | List of Remote Computer to be checked (not yet implemented) 13 | 14 | .PARAMETER Export 15 | Export to path with the computer as name 16 | 17 | .PARAMETER PassThru 18 | Returns an object representing the item with which you are working. By default, this cmdlet does not generate any pipeline output. 19 | 20 | .INPUTS 21 | You can pipe the computer into the command which is recognised by type, you can also pipe any parameter by name. It will also take the path positionally 22 | 23 | .OUTPUTS 24 | This script outputs a csv file with the result of the processing. It will optionally produce a custom object with the same information 25 | 26 | .EXAMPLE 27 | Microsoft-MissingUpdates.ps1 -Computer ADC01 28 | This checks a single a single computer and displays the result in the powershell window 29 | 30 | .EXAMPLE 31 | Microsoft-MissingUpdates.ps1 -Computer ADC01 -Export c:\ 32 | This checks a single a single computer and and export the result to C:\ADC01.txt 33 | 34 | #> 35 | function Test-PendingReboot { 36 | if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -EA Ignore) { return $true } 37 | if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -EA Ignore) { return $true } 38 | if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations -EA Ignore) { return $true } 39 | try { 40 | $util = [wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities" 41 | $status = $util.DetermineIfRebootPending() 42 | if (($status -ne $null) -and $status.RebootPending) { 43 | return $true 44 | } 45 | } 46 | catch { } 47 | 48 | return $false 49 | } 50 | 51 | Test-PendingReboot -------------------------------------------------------------------------------- /Windows/Microsoft-ProfileCleaner.ps1: -------------------------------------------------------------------------------- 1 | Param 2 | ( 3 | [switch]$RunOnce = $False 4 | ) 5 | <# 6 | .SYNOPSIS 7 | Clean up (partial) local profiles that are not in use anymore. 8 | .DESCRIPTION 9 | This script provides a way to delete local (copies) of user profiles that are not in use anymore. There is an option to exclude specified accounts. Special accounts like SYSTEM are always excluded 10 | .PARAMETER RunOnce 11 | By default, the script will loop continuously, use this parameter to run it only once 12 | .INPUTS 13 | None 14 | .OUTPUTS 15 | Logfile 16 | .NOTES 17 | Version: 1.0 18 | Author: Bart Jacobs - @Cloudsparkle 19 | Creation Date: 17/04/2020 20 | Purpose/Change: Remove obsolete local profiles 21 | 22 | .EXAMPLE 23 | None 24 | #> 25 | 26 | # Usernames to be excluded 27 | $Exclusions = "P-SVC-ACCOUNT1, P-SVC-ACCOUNT2" 28 | $Exclusions = $Exclusions.tolower() 29 | 30 | # Set the output folder for the logfile 31 | $currentDir = Split-Path $MyInvocation.MyCommand.Path 32 | $outputdir = $currentDir 33 | 34 | # Set the filename for the logfile 35 | $logfilename = "ProfilesDeleted.txt" 36 | 37 | # Path and filename for logfile 38 | $logfile = Join-Path $outputdir $logfilename 39 | 40 | # Script loop 41 | while ($true) 42 | { 43 | # Write-host "Starting scriptloop" 44 | Try {$profiles = Get-WmiObject -Class Win32_UserProfile} 45 | Catch 46 | { 47 | Write-Warning "Failed to retreive user profiles" 48 | Exit 1 49 | } 50 | 51 | ForEach ($profile in $profiles) 52 | { 53 | # Retreiving profiledata 54 | $sid = New-Object System.Security.Principal.SecurityIdentifier($profile.SID) 55 | $account = $sid.Translate([System.Security.Principal.NTAccount]) 56 | $accountDomain = $account.value.split("\")[0] 57 | $accountName = $account.value.split("\")[1] 58 | $profilePath = $profile.LocalPath 59 | $loaded = $profile.Loaded 60 | $special = $profile.Special 61 | $excluded = $false 62 | 63 | # Convert the accountname to lower, to match the exclusions 64 | $accountname = $accountname.tolower() 65 | 66 | if ($Exclusions.Contains($accountName)) 67 | { 68 | $excluded = $true 69 | } 70 | 71 | if (-Not $loaded -and -not $special -and -not $excluded) 72 | { 73 | Try 74 | { 75 | $profile.Delete() 76 | # Write-Host "Profile deleted successfully" -ForegroundColor Green 77 | $logEntry = ((Get-Date -uformat "%D %T") + " - Profile deleted successfully: " + $accountname) 78 | $logEntry | Out-File $logFile -Append 79 | } 80 | Catch 81 | { 82 | Write-Host "Error deleting profile" -ForegroundColor Red 83 | write-host $accountName -ForegroundColor Red 84 | $logEntry = ((Get-Date -uformat "%D %T") + " - ERROR Deleting profile: " + $accountname) 85 | $logEntry | Out-File $logFile -Append 86 | } 87 | } 88 | } 89 | 90 | if ($RunOnce -eq $True) 91 | { 92 | write-host "Running Once, exiting now" 93 | Exit 0 94 | } 95 | 96 | #Sleeping 30 seconds before the next run 97 | Write-Host "Sleeping..." 98 | start-sleep -s 30 99 | } -------------------------------------------------------------------------------- /Windows/Microsoft-RemoteUserLogonTime.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Get remote logon times via WMI 3 | 4 | @guyrleech (c) 2018 5 | #> 6 | 7 | <# 8 | .SYNOPSIS 9 | 10 | Use WMI to query computers to find out, since boot, when any remote desktop connections logged on 11 | 12 | .PARAMETER computers 13 | 14 | A comma separated list of computers to query 15 | 16 | .PARAMETER user 17 | 18 | A specific user to query otherwise all users who have logged on are returned 19 | 20 | .PARAMETER before 21 | 22 | Only sessions logged on on or before this date/time are returned 23 | 24 | .PARAMETER after 25 | 26 | Only sessions logged on on or after this date/time are returned 27 | 28 | .EXAMPLE 29 | 30 | & '\Microsoft-RemoteUserLogonTime.ps1' 31 | 32 | Show all remote desktop logons since boot on the current computer 33 | 34 | .EXAMPLE 35 | 36 | & '\Microsoft-RemoteUserLogonTime.ps1' -Computers computer01,computer02 -user guy.leech -After "01/08/2018" 37 | 38 | Show all remote desktop logons for user guy.leech on computers computer01 and computer02 since 1st August 2018 39 | 40 | .NOTES 41 | 42 | Pipe the output through cmdlets like Out-GridView, Export-CSV or Format-Table 43 | #> 44 | 45 | [CmdletBinding()] 46 | 47 | Param 48 | ( 49 | [string[]]$computers = @( $env:COMPUTERNAME ) , 50 | [string]$user , 51 | [datetime]$after , 52 | [datetime]$before 53 | ) 54 | 55 | $culture = Get-Culture 56 | [string]$preciseTimeFormat = '{0}.FFFFFFF' -f $culture.DateTimeFormat.SortableDateTimePattern 57 | 58 | ForEach( $computer in $computers ) 59 | { 60 | Get-WmiObject win32_logonsession -ComputerName $computer -Filter "LogonType='10'" | ForEach-Object ` 61 | { 62 | $session = $_ 63 | Get-WmiObject win32_loggedonuser -ComputerName $computer -filter "Dependent = '\\\\.\\root\\cimv2:Win32_LogonSession.LogonId=`"$($session.logonid)`"'" | ForEach-Object ` 64 | { 65 | [datetime]$logonTime = (([WMI] '').ConvertToDateTime( $session.StartTime )) 66 | if( ( ! $PSBoundParameters[ 'after'] -or $logonTime -ge $after ) ` 67 | -and ( ! $PSBoundParameters[ 'before' ] -or $logonTime -le $before ) ` 68 | -and $_.Antecedent -match 'Domain="(.*)",Name="(.*)"$' ` 69 | -and ( [string]::IsNullOrEmpty( $user ) -or ( $matches -and $matches.Count -eq 3 -and $user -eq $matches[2] ) ) ) 70 | { 71 | [pscustomobject]@{ 'Computer' = $computer ; 'User' = ($matches[1] + '\' + $matches[2] ) ; 'Logon Time' = $logonTime ; 'Logon Time Precise' = (Get-Date $logonTime -Format $preciseTimeFormat ) ;'Authentication Package' = $session.AuthenticationPackage } 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Windows/Microsoft-ResetStartmenu.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .FUNCTIONALITY 3 | Resets Win10 start-menu left side and right-side 4 | .SYNOPSIS 5 | Left-side: Removes orphaned entries that appear randomly and when MS Store (Appx) based apps are cached inside FSLogix profiles, but removed from actual OS 6 | Right-side (tile section): performs resets based on build XML 7 | .DESCRIPTION 8 | Author owen.reynolds@procontact.ca & jonathan.pitre@procontact.ca 9 | .EXAMPLE 10 | ./Reset-StartMenu.ps1 11 | .NOTES 12 | .Link 13 | N/A 14 | #> 15 | 16 | Add-Type -AssemblyName System.Windows.Forms 17 | 18 | #Button Legend 19 | # OK 0 20 | # OKCancel 1 21 | # AbortRetryIgnore 2 22 | # YesNoCancel 3 23 | # YesNo 4 24 | # RetryCancel 5 25 | 26 | #Icon legend 27 | # None 0 28 | # Hand 16 29 | # Error 16 30 | # Stop 16 31 | # Question 32 32 | # Exclamation 48 33 | # Warning 48 34 | # Asterisk 64 35 | # Information 64 36 | 37 | 38 | $messageBoxTitle = "Windows Start Menu Reset" 39 | $UserResponse = [System.Windows.Forms.MessageBox]::Show("Do you want to reset the Windows Start Menu to resolve issues with missing/invalid shortcuts?" , $messageBoxTitle, 4, 32) 40 | 41 | If ($UserResponse -eq "YES" ) { 42 | 43 | Write-Host "Reseting windows left / right side Windows 10 icons/allignment" 44 | 45 | Copy-Item -Path "C:\Users\Default\AppData\Local\Microsoft\Windows\Shell\LayoutModification.xml" -Destination "$env:LOCALAPPDATA\Microsoft\Windows\Shell" -Force 46 | Remove-Item 'HKCU:\Software\Microsoft\Windows\CurrentVersion\CloudStore\Store\Cache\DefaultAccount\*$start.tilegrid$windows.data.curatedtilecollection.tilecollection' -Force -Recurse -ErrorAction SilentlyContinue 47 | 48 | Get-Process shellexperiencehost -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue 49 | 50 | Write-Host "Pausing for for 3 seconds..." 51 | Start-Sleep -Seconds 3 52 | 53 | Remove-Item -Path "$env:LOCALAPPDATA\Packages\Microsoft.Windows.ShellExperienceHost_cw5n1h2txyewy\TempState\StartUnifiedTileModelCache.dat" -Force -ErrorAction SilentlyContinue 54 | Remove-Item -Path "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\TempState\StartUnifiedTileModelCache.dat" -Force -ErrorAction SilentlyContinue 55 | 56 | Get-Process Explorer | Stop-Process -Force -ErrorAction SilentlyContinue 57 | [System.Windows.Forms.MessageBox]::Show("Windows Start Menu was reset!", $messageBoxTitle, 0, 64) 58 | } 59 | Else { 60 | Write-Host "No" 61 | Exit 62 | } -------------------------------------------------------------------------------- /Windows/Microsoft-SetNewUserProfile.ps1: -------------------------------------------------------------------------------- 1 | # Hide powershell prompt 2 | Add-Type -Name win -MemberDefinition '[DllImport("user32.dll")] public static extern bool ShowWindow(int handle, int state);' -Namespace native 3 | [native.win]::ShowWindow(([System.Diagnostics.Process]::GetCurrentProcess() | Get-Process).MainWindowHandle,0) 4 | 5 | # Custom modules list 6 | $Modules = @("ActiveDirectory") 7 | 8 | # Install and import custom modules list 9 | Foreach ($Module in $Modules) { 10 | If (-not(Get-Module -ListAvailable -Name $Module)) {Import-Module -Name $Module} 11 | } 12 | 13 | Function Get-ScriptDirectory { 14 | If ($psISE) {Split-Path $psISE.CurrentFile.FullPath} 15 | Else {$Global:PSScriptRoot} 16 | } 17 | 18 | # Variables Declaration 19 | # Generic 20 | $ProgressPreference = "SilentlyContinue" 21 | $ErrorActionPreference = "SilentlyContinue" 22 | $appScriptDirectory = Get-ScriptDirectory 23 | $env:SEE_MASK_NOZONECHECKS = 1 24 | $User = $env:USERNAME 25 | $UserProperties = Get-ADUser -Filter {SamAccountName -like $User} -Properties SamAccountName, ProfilePath | Select-Object SamAccountName, ProfilePath 26 | # Env specific 27 | $FileShare1 = "\\mc50f004\DATAS ADMIN" 28 | $FileShare2 = "\\mc50f004\datas adminqc" 29 | $FileShare3 = "\\mc50f004\DATAS INFO" 30 | 31 | # Backup Firefox bookmarks 32 | New-Item -Path "$env:USERPROFILE\Documents\Firefox" -ItemType Directory -Force 33 | If (Test-Path -Path "$($UserProperties.ProfilePath)AppData\Roaming\Mozilla\Firefox") { 34 | $FirefoxBookmarks = "$($UserProperties.ProfilePath)AppData\Roaming\Mozilla\Firefox\Profiles\*\places.sqlite" 35 | Copy-Item -Path $FirefoxBookmarks -Destination "$env:USERPROFILE\Documents\Firefox\places.sqlite" -Force -Recurse 36 | } 37 | ElseIf (Test-Path -Path "$FileShare1\$User\AppData\Roaming\Mozilla\Firefox") { 38 | $FirefoxBookmarks = "$FileShare1\$User\AppData\Roaming\Mozilla\Firefox\Profiles\*\places.sqlite" 39 | Copy-Item -Path $FirefoxBookmarks -Destination "$env:USERPROFILE\Documents\Firefox\places.sqlite" -Force -Recurse 40 | } 41 | ElseIf (Test-Path -Path "$FileShare2\$User\AppData\Roaming\Mozilla\Firefox") { 42 | $FirefoxBookmarks = "$FileShare2\$User\AppData\Roaming\Mozilla\Firefox\Profiles\*\places.sqlite" 43 | Copy-Item -Path $FirefoxBookmarks -Destination "$env:USERPROFILE\Documents\Firefox\places.sqlite" -Force -Recurse 44 | } 45 | ElseIf (Test-Path -Path "$FileShare3\$User\AppData\Roaming\Mozilla\Firefox") { 46 | $FirefoxBookmarks = "$FileShare3\$User\AppData\Roaming\Mozilla\Firefox\Profiles\*\places.sqlite" 47 | Copy-Item -Path $FirefoxBookmarks -Destination "$env:USERPROFILE\Documents\Firefox\places.sqlite" -Force -Recurse 48 | } 49 | 50 | # Detect Outlook Signnature location 51 | If (Test-Path -Path "$($UserProperties.ProfilePath)AppData\Roaming\Microsoft\Signatures") { 52 | $OutlookSignature = "$($UserProperties.ProfilePath)AppData\Roaming\Microsoft\Signatures" 53 | } 54 | ElseIf (Test-Path -Path "$FileShare1\$User\AppData\Roaming\Microsoft\Signatures") { 55 | $OutlookSignature = "$FileShare1\$User\AppData\Roaming\Microsoft\Signatures" 56 | } 57 | ElseIf (Test-Path -Path "$FileShare2\$User\AppData\Roaming\Microsoft\Signatures") { 58 | $OutlookSignature = "$FileShare2\$User\AppData\Roaming\Microsoft\Signatures" 59 | } 60 | ElseIf (Test-Path -Path "$FileShare3\$User\AppData\Roaming\Microsoft\Signatures") { 61 | $OutlookSignature = "$FileShare3\$User\AppData\Roaming\Microsoft\Signatures" 62 | } 63 | 64 | # Copy and Assign Outlook Signature 65 | If (Test-Path -Path $OutlookSignature\*.htm) { 66 | Copy-Item -Path $OutlookSignature -Destination "$env:APPDATA\Microsoft" -Force -Recurse 67 | Remove-ItemProperty -Path "HKCU:\Software\Microsoft\Office\16.0\Outlook\Setup" -Name "First-Run" -Force 68 | $SigPath = (Get-ChildItem -Path "$env:APPDATA\Microsoft\Signatures\*.htm" | Select-Object -ExpandProperty Name).Split(".")[0] 69 | New-Item -Path "HKCU:\Software\Microsoft\Office\16.0\Common\MailSettings" -Value "default value" -Force 70 | New-ItemProperty -Path "HKCU:\Software\Microsoft\Office\16.0\Common\MailSettings" -Name "NewSignature" -Value $SigPath -PropertyType "String" -Force 71 | #New-ItemProperty -Path "HKCU:\Software\Microsoft\Office\16.0\Common\MailSettings" -Name "ReplySignature" -Value $SigPath -PropertyType "String" -Force 72 | } 73 | Else { 74 | Write-Verbose -Message 'No Outlook signatures was found.' 75 | } 76 | 77 | If (Get-Process | Where-Object Name -eq Outlook) { 78 | Write-Verbose -Message 'Outlook is already running. No action needed.' 79 | } 80 | Else { 81 | $Key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\OUTLOOK.EXE\' 82 | 83 | If (!(Test-Path -Path $Key)) { 84 | throw 'Path to Outlook executable not found.' 85 | 86 | } Else { 87 | $exe = (Get-ItemProperty -Path $Key).'(default)' 88 | If (Test-Path -Path $exe) { 89 | Write-Verbose -Message 'Starting Outlook application...' 90 | Start-Process -FilePath $exe -ArgumentList "/resetfoldernames /recycle" 91 | } Else { 92 | Throw 'Outlook executable not found.' 93 | } 94 | } 95 | } 96 | 97 | 98 | If (Get-Process | Where-Object name -eq OneDrive) { 99 | Write-Verbose -Message 'OneDrive is already running. Process will be killed.' 100 | Stop-Process -Name OneDrive -Force 101 | } 102 | 103 | If (!(Test-Path -Path "${env:ProgramFiles(x86)}\Microsoft OneDrive\OneDrive.exe")) { 104 | throw 'Path to OneDrive executable not found.' 105 | } 106 | Else { 107 | Write-Verbose -Message 'Starting OneDrive application...' 108 | Start-Process -FilePath "${env:ProgramFiles(x86)}\Microsoft OneDrive\OneDrive.exe" -ArgumentList "/background /setautostart" 109 | } -------------------------------------------------------------------------------- /Windows/Microsoft-StartUpCopyFTA.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Copies FSLogix Rules from from a network location to the FSLogix Rule location on the local computer 4 | .DESCRIPTION 5 | This script is used to copy FSLogix Rules from a network location to the FSLogix Rule location on the local computer. The script is intended to be used with 6 | a GPO that runs the script at startup. 7 | The script includes error handling that will write errors to the local Application Event Log. Modify the Write Event Log variables as needed. 8 | .NOTES 9 | Author : Jonathan Pitre, original script by Travis Roberts www.ciraltos.com 10 | Version : 1.1 11 | #> 12 | 13 | ######## Variables ########## 14 | # Source path for FSLogix App Masking rules 15 | $sourcePath = "\\%DomainName%\NETLOGON\FSLogix\Rules" 16 | # Destination path for FSLogix App Masking rules 17 | $destinationPath = "$env:ProgramFiles\FSLogix\Apps\Rules" 18 | # Source path for File Association XML 19 | $sourcePath2 = "\\%DomainName%\NETLOGON\FileAssociation" 20 | # Destination path for File Association XML 21 | $destinationPath2 = "$env:winDir\system32" 22 | ######## Write Event Log ########## 23 | # Set Variables 24 | $eventLog = "Application" 25 | $eventSource = "FSLogix Rules Copy" 26 | $eventID = 4000 27 | $entryType = "Error" 28 | 29 | # Check if the source exists and create if needed 30 | If ([System.Diagnostics.EventLog]::SourceExists($eventSource) -eq $False) { 31 | New-EventLog -LogName Application -Source $eventSource 32 | } 33 | 34 | # Write EventLog Function 35 | Function Write-AppEventLog { 36 | Param($errorMessage) 37 | Write-EventLog -LogName $eventLog -EventID $eventID -EntryType $entryType -Source $eventSource -Message $errorMessage 38 | } 39 | 40 | # Check the source path 41 | if ((Test-Path $SourcePath) -eq $False) { 42 | write-AppEventLog 'Source path not found or not accessible' 43 | } 44 | 45 | # Check the destination path 46 | If ((Test-Path $destinationPath) -eq $False) { 47 | Write-AppEventLog 'Destination path not found or not accessible' 48 | } 49 | 50 | # Copy the files to the destination 51 | Try { 52 | Remove-Item $destinationPath\*.* -Force 53 | Copy-Item -ErrorAction Stop -Path "$sourcePath\*.fxa" -Destination $destinationPath -Force 54 | Copy-Item -ErrorAction Stop -Path "$sourcePath\*.fxr" -Destination $destinationPath -Force 55 | } 56 | Catch { 57 | $ErrorMessage = $_.Exception.message 58 | Write-AppEventLog $ErrorMessage 59 | } 60 | 61 | 62 | # Copy File Association xml 63 | Copy-Item -ErrorAction Stop -Path "$sourcePath2\FileAssociation2019.xml" -Destination "$destinationPath2\FileAssociation.xml" -Force -------------------------------------------------------------------------------- /Windows/README.md: -------------------------------------------------------------------------------- 1 | # Microsoft Windows 2 | 3 | ## Microsoft-Autorun.ps1 4 | List/add/remove autorun entries, so items run at logon, in start menu or registry, for user running the script or all users. 5 | 6 | ## Microsoft-CheckFixDomainMembership.ps1 7 | Check domain membership is ok and if not fix via stored credentials 8 | 9 | ## Microsoft-CheckStartProcesses.ps1 10 | Check if any of a list of processes are not running and either just alert, prompt whether to run or just run based on parameters 11 | 12 | ## Microsoft-DeleteProfileForGroupMember.ps1 13 | Delete profiles for members of an AD group which aren't in use 14 | 15 | ## Microsoft-RemoteUserLogonTime.ps1 16 | Use WMI to query computers to find out, since boot, when any remote desktop connections logged on 17 | 18 | ## Microsoft-InfoViaCIM.ps1 19 | Gather info from one or more computers via CIM and write to CSV file 20 | 21 | ## Microsoft-InstalledSoftware.ps1 22 | Retrieve information on installed programs 23 | 24 | ## Microsoft-GroupMembershipModifier.ps1 25 | Add or remove accounts from groups on Windows machines. 26 | 27 | ## Microsoft-LocalProfileCleaner.ps1 28 | Add or remove accounts from groups on Windows machines. 29 | 30 | ## Microsoft-LocalRedirectedFolder.ps1 31 | Redirect a user's special folders by calling the SHSetKnownFolderPath API or show existing folder redirections 32 | 33 | ## Microsoft-UserSessions.ps1 34 | Report the user sessions, as returned by quser, from the machines in Active Directory that match the regular expression specified via -name 35 | 36 | ## Microsoft-EventAggregator.ps1 37 | Retrieve all events from all 300+ event logs in a given time/date range and show in a sortable/filterable gridview or export to csv 38 | 39 | ## Microsoft-KillProcess.ps1 40 | Kills a process, if this process reaches a specific cpu limit and write a log file 41 | --------------------------------------------------------------------------------