Powershell

Validate-GroupMembership Powershell Function

Use the Validate-GroupMembership function to confirm whether or not a user or computer object is a member of an AD group.

Examples:
Find out if the current user is a member of an AD group called “Test Group”
Validate-GroupMembership -SearchString $env:USERNAME -SearchType User -Group “Test Group”

Find out if the current computer is a member of an AD group called “ORL Computers”
Validate-GroupMembership -SearchString $env:COMPUTERNAME -SearchType Computer -Group “ORL Computers”

Function Validate-GroupMembership {

    <#

    .SYNOPSIS
    Validates AD group membership for a user or computer object
  
    .PARAMETER SearchString
    Provide Username or Computer Name
  
    .PARAMETER SearchType
    Specify type (User or Computer)

    .PARAMETER Group
    Provide AD Group name
  
    .EXAMPLE
    Validate-GroupMembership -SearchString $env:USERNAME -SearchType User -Group "Test Group"
  
    .EXAMPLE
    Validate-GroupMembership -SearchString $env:COMPUTERNAME -SearchType Computer -Group "ORL Computers"

    #>

    param (
     [parameter(Mandatory=$True)]
     [ValidateNotNullOrEmpty()]$SearchString,
     [parameter(Mandatory=$True)]
     [ValidateSet("User", "Computer")]
     [ValidateNotNullOrEmpty()]$SearchType,
     [parameter(Mandatory=$true)]
     [ValidateNotNullOrEmpty()]$Group
    )

    Try {

        $objSearcher = New-Object System.DirectoryServices.DirectorySearcher
        $objSearcher.SearchRoot = New-Object System.DirectoryServices.DirectoryEntry

        If ($SearchType -eq "User") {

            $objSearcher.Filter = "(&(objectCategory=User)(SAMAccountName=$SearchString))"

        } 
        Else {

            $objSearcher.Filter = "(&(objectCategory=Computer)(cn=$SearchString))"

        }

        $objSearcher.SearchScope = "Subtree"
        $obj = $objSearcher.FindOne()
        $User = $obj.Properties["distinguishedname"]

        $objSearcher.PageSize=1000
        $objSearcher.Filter = "(&(objectClass=group)(cn=$Group))"
        $obj = $objSearcher.FindOne()

        [String[]]$Members = $obj.Properties["member"]

        If($Members.count -eq 0) {                       

            $retrievedAllMembers=$false           
            $rangeBottom =0
            $rangeTop= 0

            While (! $retrievedAllMembers) {

                $rangeTop=$rangeBottom + 1499               

                $memberRange="member;range=$rangeBottom-$rangeTop"  

                $objSearcher.PropertiesToLoad.Clear()
                [void]$objSearcher.PropertiesToLoad.Add("$memberRange")

                $rangeBottom+=1500

                Try {

                    $obj = $objSearcher.FindOne() 
                    $rangedProperty = $obj.Properties.PropertyNames -like "member;range=*"
                    $Members +=$obj.Properties.item($rangedProperty)          
                   
                        if ($Members.count -eq 0) { $retrievedAllMembers=$true }
                }

                Catch {

                    $retrievedAllMembers=$true
                }

            }
            
        }

    }

    Catch {

        Write-Host "Either group or user does not exist"
        Return $False

    }
   
    If ($Members -contains $User) { 

        Return $True

    }
    Else {

        Return $False

    }

}

Add a new custom Powershell module path

You can use the following script to add a new module path to the PSModulePath environmental variable. Adding modules to this path will allow you to use them in your own scripts and if you have Powershell 3.0+ these modules will be automatically loaded when you call one of the custom CMDLET’s.

Notes:
Please replace the $ModulePath variable with the path that you would like to use.

$ModulePath = "YOUR PATH HERE"
$Path = (Get-ItemProperty -Path "HKLM:\SYSTEM\ControlSet001\Control\Session Manager\Environment").PSModulePath
$NewPath = "$ModulePath" + ";" + $Path

If($Path -notlike "*$ModulePath*") {
    Set-ItemProperty -Path "HKLM:\SYSTEM\ControlSet001\Control\Session Manager\Environment" -Name PSModulePath -Type String -Value "$NewPath"
}

AC Power Check For Laptops

The following script can be used in an SCCM or MDT upgrade task sequence to check if a laptop is connected to a charger. If the script detects that the laptop is not connected to a charger, it will prompt the user to connect the laptop to AC power.

$ChassisTypes = (Get-WmiObject -Class Win32_SystemEnclosure).ChassisTypes

Switch($ChassisTypes) {

    3 { $Chassis = "Desktop" }
    4 { $Chassis = "Desktop" }
    5 { $Chassis = "Desktop" }
    6 { $Chassis = "Desktop" }
    7 { $Chassis = "Desktop" }
    8 { $Chassis = "Laptop" }
    9 { $Chassis = "Laptop" }
    10 { $Chassis = "Laptop" }
    11 { $Chassis = "Laptop" }
    12 { $Chassis = "Laptop" }
    14 { $Chassis = "Laptop" }
    15 { $Chassis = "Desktop" }
    16 { $Chassis = "Desktop" }
    18 { $Chassis = "Laptop" }
    21 { $Chassis = "Laptop" }
    23 { $Chassis = "Server" }
    31 { $Chassis = "Laptop" }

}
If($Chassis -eq "Laptop") {

    Do {
      $PowerStatus = (Get-WmiObject -Class BatteryStatus  -Namespace root\wmi -ErrorAction SilentlyContinue).PowerOnLine

        If($PowerStatus -ne $True) {

            $TSEnv = New-Object -ComObject "Microsoft.SMS.TsProgressUI"
            $TSEnv.CloseProgressDialog()
            $wshell = New-Object -ComObject Wscript.Shell
            $wshell.Popup("Please Connect AC Power - Click OK to Continue",0,"AC Power Check",0)
    
        }
    }
    Until($PowerStatus -eq $True)

}

Troubleshooting Script for Windows 10 Start Menu Issues

Since a lot of people are having issues with the start menu tiles in their images, I decided to create the following script to help others troubleshoot some common issues that may occur.

Note: This script is compatible with Windows 10 1709 and above.

The script will run through the following checks:

  • Checks to see if C:\Users\Default\AppData\Local\Microsoft\Windows\Shell\LayoutModification.xml exists
  • Checks if the current user’s LayoutModification.xml matches the default profile’s LayoutModification.xml.
  • Opens C:\Users\Default\AppData\Local\Microsoft\Windows\Shell\LayoutModification.xml so you can confirm whether or not this is the XML file that you imported in your OSD process.
  • Checks to see if HKCU:\Software\Microsoft\Windows\CurrentVersion\CloudStore\Store\Cache\DefaultAccount\$start.tilegrid$windows.data.curatedtilecollection.root is causing the issue.


Download The Script Now!

$CurrentUserStartMenu = "$env:LOCALAPPDATA\Microsoft\Windows\Shell\LayoutModification.xml"
$DefaultStartMenu = "C:\Users\Default\AppData\Local\Microsoft\Windows\Shell\LayoutModification.xml"


If((Test-Path "$DefaultStartMenu") -eq $False) {

    Write-Host -ForegroundColor Red "$DefaultStartMenu does not exist!"
    Write-Host -ForegroundColor Green "Possible Solution - Use Import-StartLayout to import your start layout (You will need to login as a new user to see the changes)"
    
    $Prompt = Read-Host -Prompt "Press any key to exit"
        
    If($Prompt -ne $Null) {

        Return

    }

}
Else {

    If((Get-FileHash $CurrentUserStartMenu).hash -ne (Get-FileHash $DefaultStartMenu).hash){

        Write-Host "The default profile layoutmodification.xml and the current user's layoutmodification.xml do not match!" -ForegroundColor Red
        Copy-Item "C:\Users\Default\AppData\Local\Microsoft\Windows\Shell\LayoutModification.xml" "$env:LOCALAPPDATA\Microsoft\Windows\Shell\LayoutModification.xml" -Force
        Remove-Item 'HKCU:\Software\Microsoft\Windows\CurrentVersion\CloudStore\Store\Cache\DefaultAccount\$start.tilegrid$windows.data.curatedtilecollection.root' -Force -Recurse
        Get-Process Explorer | Stop-Process

        $Prompt = Read-Host -Prompt "Is your custom start layout visible when you launch the start menu? (YES, NO)"

        If($Prompt -like "Y*") {

            Write-Host "Solution - Copying the default profile's layoutmodification to $env:LOCALAPPDATA\Microsoft\Windows\Shell\LayoutModification.xml fixed the problem" -ForegroundColor Red
        
            $Prompt = Read-Host -Prompt "Press any key to exit"
        
            If($Prompt -ne $Null) {

                Return

            }

        }
        Else {

            Write-Host "Unable to determine a solution" -ForegroundColor Red
        
            $Prompt = Read-Host -Prompt "Press any key to exit"
        
            If($Prompt -ne $Null) {

                Return

            }

        }


    }

    Write-Host -ForegroundColor Red "Confirm that the default start layout is the same start layout that you imported"
    Start-Process Notepad -ArgumentList "$DefaultStartMenu"

    $Prompt = Read-Host -Prompt "Is this the same layout that you imported? (YES, NO)"

    If($Prompt -like "Y*") {

        Remove-Item 'HKCU:\Software\Microsoft\Windows\CurrentVersion\CloudStore\Store\Cache\DefaultAccount\$start.tilegrid$windows.data.curatedtilecollection.root' -Force -Recurse
        Get-Process Explorer | Stop-Process
        
        $Prompt = Read-Host -Prompt "Is your custom start layout visible when you launch the start menu? (YES, NO)"

            If($Prompt -like "Y*") {

                Write-Host 'Possible Solution - Delete Software\Microsoft\Windows\CurrentVersion\CloudStore\Store\Cache\DefaultAccount\$start.tilegrid$windows.data.curatedtilecollection.root from C:\Users\Default\NTUser.dat' -ForegroundColor Green
                
                $Prompt = Read-Host -Prompt "Press any key to exit"
        
                If($Prompt -ne $Null) {

                    Return

                }

            }
            Else {

                Write-Host "Unable to determine a solution" -ForegroundColor Red
                
                $Prompt = Read-Host -Prompt "Press any key to exit"
        
                If($Prompt -ne $Null) {

                    Return

                }

            }

    }

    Else {
        
        Write-Host "Possible Solution - Replace C:\Users\Default\AppData\Local\Microsoft\Windows\Shell\LayoutModification.xml with your custom LayoutModification.xml(You will need to login as a new user to see the changes)" -ForegroundColor Green
        
        $Prompt = Read-Host -Prompt "Press any key to exit"
        
        If($Prompt -ne $Null) {

            Return

        }

    }

}

How to reset your start menu layout in Windows 10 1709

The following short script will reset your start menu layout back to it’s default configuration.

Remove-Item 'HKCU:\Software\Microsoft\Windows\CurrentVersion\CloudStore\Store\Cache\DefaultAccount\$start.tilegrid$windows.data.curatedtilecollection.root' -Force -Recurse
Get-Process Explorer | Stop-Process

Are you trying to reset the start layout for Windows 10 1809? Click here to find out how.

Removing Duplicate Windows Boot Manager Entries in your MDT Task Sequence

We recently purchased quite a few Dell XPS 13 9365 laptops and while testing our image; I noticed that the list of Windows Boot Manager entries in the BIOS was growing each time I would image the laptop. In order to get around this, I had to create the following script that would automatically scan through the Boot Configuration Data (BCD) store and remove all of the duplicate GUID’s that appear after running the image.

# CONFIGURE MDT LOGGING
$TSenv = New-Object -COMObject Microsoft.SMS.TSEnvironment 
$LogPath = $TSenv.Value("LogPath")  
$LogFile = "$LogPath\WindowsBootManagerRemoval.log"

Start-Transcript $LogFile

# LOCATE GUID
$Identifiers = BCDEDIT /ENUM FIRMWARE | Select-String "identifier" | ForEach-Object { $_ -replace "identifier" }

If($Identifiers -ne $Null) {
    
    $IdentifierList = $Identifiers.Replace(" ","") | Where-Object {$_ -notcontains "{fwbootmgr}" -and $_ -notcontains "{bootmgr}"}
    
    Write-Host "" 
    Write-Host "--------------------------------------------------------------------------------" 
    Write-Host "PREPARING TO REMOVE DUPLICATE WINDOWS BOOT MANAGER ENTRIES"
    Write-Host "--------------------------------------------------------------------------------" 
    Write-Host "" 

    # REMOVE GUIDS
    ForEach($Identifier in $IdentifierList) {
        BCDEDIT /Delete $Identifier
        Write-Host "DELETED $Identifier"
    }
}
Else {
    Write-Host "SKIPPING - COULD NOT LOCATE DUPLICATE WINDOWS BOOT MANAGER ENTRIES"
}

Once you are ready to use the script, go ahead and copy it over to your Deployment Share and add it to your State Restore group in your MDT Task Sequence.
MDT Task Sequence

Since this was not happening to all of our computer models, I made sure to add a Task Sequence condition that forced this step to only run for the Dell XPS 13 9365 laptops.

Feel free to leave any questions below!

Set-Wallpaper Powershell Function

The following Powershell function will change the current user’s desktop wallpaper automatically using the SystemParametersInfo function that can be located in the User32.dll.

Function Set-WallPaper($Image) {
<#

    .SYNOPSIS
    Applies a specified wallpaper to the current user's desktop
   
    .PARAMETER Image
    Provide the exact path to the image
 
    .EXAMPLE
    Set-WallPaper -Image "C:\Wallpaper\Default.jpg"
 
#>
 
Add-Type -TypeDefinition @" 
using System; 
using System.Runtime.InteropServices;
 
public class Params
{ 
    [DllImport("User32.dll",CharSet=CharSet.Unicode)] 
    public static extern int SystemParametersInfo (Int32 uAction, 
                                                   Int32 uParam, 
                                                   String lpvParam, 
                                                   Int32 fuWinIni);
}
"@ 
 
    $SPI_SETDESKWALLPAPER = 0x0014
    $UpdateIniFile = 0x01
    $SendChangeEvent = 0x02
 
    $fWinIni = $UpdateIniFile -bor $SendChangeEvent
 
    $ret = [Params]::SystemParametersInfo($SPI_SETDESKWALLPAPER, 0, $Image, $fWinIni)

}

Example:
Set-WallPaper -Image “C:\Wallpaper\Default.jpg”

For more information about the SystemParametersInfo function, please see this link to MSDN.

Update 08/10/2020:

Per request, I have included a new parameter for the Set-Wallpaper function to configure wallpaper styles.  See the updated function below:

Function Set-WallPaper {

<#

    .SYNOPSIS
    Applies a specified wallpaper to the current user's desktop
   
    .PARAMETER Image
    Provide the exact path to the image

    .PARAMETER Style
    Provide wallpaper style (Example: Fill, Fit, Stretch, Tile, Center, or Span)
 
    .EXAMPLE
    Set-WallPaper -Image "C:\Wallpaper\Default.jpg"
    Set-WallPaper -Image "C:\Wallpaper\Background.jpg" -Style Fit
 
#>

param (
    [parameter(Mandatory=$True)]
    # Provide path to image
    [string]$Image,
    # Provide wallpaper style that you would like applied
    [parameter(Mandatory=$False)]
    [ValidateSet('Fill', 'Fit', 'Stretch', 'Tile', 'Center', 'Span')]
    [string]$Style
)

$WallpaperStyle = Switch ($Style) {
 
    "Fill" {"10"}
    "Fit" {"6"}
    "Stretch" {"2"}
    "Tile" {"0"}
    "Center" {"0"}
    "Span" {"22"}
 
}

If($Style -eq "Tile") {

    New-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name WallpaperStyle -PropertyType String -Value $WallpaperStyle -Force
    New-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name TileWallpaper -PropertyType String -Value 1 -Force

}
Else {

    New-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name WallpaperStyle -PropertyType String -Value $WallpaperStyle -Force
    New-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name TileWallpaper -PropertyType String -Value 0 -Force

}

Add-Type -TypeDefinition @" 
using System; 
using System.Runtime.InteropServices;
 
public class Params
{ 
    [DllImport("User32.dll",CharSet=CharSet.Unicode)] 
    public static extern int SystemParametersInfo (Int32 uAction, 
                                                   Int32 uParam, 
                                                   String lpvParam, 
                                                   Int32 fuWinIni);
}
"@ 
 
    $SPI_SETDESKWALLPAPER = 0x0014
    $UpdateIniFile = 0x01
    $SendChangeEvent = 0x02
 
    $fWinIni = $UpdateIniFile -bor $SendChangeEvent
 
    $ret = [Params]::SystemParametersInfo($SPI_SETDESKWALLPAPER, 0, $Image, $fWinIni)
}

Set-WallPaper -Image "C:\Wallpaper\Background.jpg" -Style Fit

Reboot computer if up time is greater than 5 days

The following script will reboot a computer if it has an up time greater than 5 days and does not currently have a user logged on.

Function Get-TimeStamp {
     
    Return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)   
      
}

$LastBootUpTime = Get-WmiObject win32_operatingsystem
$Uptime = ((Get-Date) - ($LastBootUpTime.ConvertToDateTime($LastBootUpTime.LastBootUpTime))).Days

If($Uptime -gt 5) {
    $Explorer = Get-WmiObject Win32_Process -Filter "Name = 'Explorer.exe'"

    If($Explorer -eq $null) {
        Write-Output "$(Get-TimeStamp) - Uptime = $Uptime Days" >> C:\TEMP\ForcedReboot.log
        Write-Output "$(Get-TimeStamp) - Reboot Initiated" >> C:\TEMP\ForcedReboot.log
        Restart-Computer
    
    }
    Else {
        Write-Output "$(Get-TimeStamp) - Uptime = $Uptime Days" >> C:\TEMP\ForcedReboot.log
        Write-Output "$(Get-TimeStamp) - User is currently logged on.  Reboot has been postponed" >> C:\TEMP\ForcedReboot.log
    }
}

Windows 10 Creators Update 1703 Cleanup Script

The following script is intended to run after an in place upgrade (Ex: 1607 to 1703). If you do not know how to run a post script after an upgrade, please refer to my previous post here.

The Powershell script will copy your old wallpapers from C:\Windows.old\windows\Web\Wallpaper\Windows\ and C:\Windows.old\windows\Web\4K\Wallpaper\Windows\ to their appropriate folders. It will also uninstall OneDrive, and prevent OneDriveSetup.exe and Windows Defender from running at logon. As well as remove the Contact Support application, move Office 2016 applications to their appropriate folder in the Start Menu (During my testing, these shortcuts moved around randomly), and attempt to remove any new apps that have reappeared with the upgrade.

Logging is enabled in the script and the entire cleanup log can be located in C:\Logs\1703-Upgrade.log

<#  

.FUNCTIONS
    1 - Set-FilePermissions
        Configures file permissions

    2 - Set-FileOwnership
        Configures ownership of files

    3 - Get-TimeStamp
        Configures timestamp for logs

    4 - Write-Log
        Creates a log for the script

#>

# Configure Functions

Function Set-FilePermissions {
    param (
     [parameter(Mandatory=$true)]
     [ValidateNotNullOrEmpty()]$File,
     [parameter(Mandatory=$true)]
     [ValidateNotNullOrEmpty()]$User,
     [parameter(Mandatory=$true)]
     [ValidateNotNullOrEmpty()]$Control,
     [parameter(Mandatory=$true)]
     [ValidateNotNullOrEmpty()]$Access
    )

    $ACL = Get-ACL "$File"
    Set-Acl -Path "$File" -AclObject $ACL
    $Permission = New-Object  system.security.accesscontrol.filesystemaccessrule("$User","$Control","$Access")
    $Acl.SetAccessRule($Permission)
    Set-Acl -Path "$File" -AclObject $ACL

}

Function Set-FileOwnership {
    param (
     [parameter(Mandatory=$true)]
     [ValidateNotNullOrEmpty()]$File,
     [parameter(Mandatory=$true)]
     [ValidateNotNullOrEmpty()]$User
    )

    $ACL = Get-ACL "$File"
    $Group = New-Object System.Security.Principal.NTAccount("$User")
    $ACL.SetOwner($Group)
    Set-Acl -Path "$File" -AclObject $ACL

}

function Get-TimeStamp {
    
    return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)
    
}

function Write-Log {
    param (
    [parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]$Passed,
    [parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]$Failed
    )
    
    If ($ProcessError.Count -eq 0) {
        Write-Output "$(Get-TimeStamp) $Passed" >> C:\Logs\1703-Upgrade.log
    }
    Else {
        Write-Output "$(Get-TimeStamp) $Failed" >> C:\Logs\1703-Upgrade.log
        $Global:Errors++
        $ProcessError.Clear()
    }
}

<# -- Script begins below --  #>

$Errors = 0

Write-Output "$(Get-TimeStamp) CLEANUP SCRIPT STARTED" > C:\Logs\1703-Upgrade.log
Write-Output " " >> C:\Logs\1703-Upgrade.log
Write-Output "$(Get-TimeStamp) START COPYING WALLPAPERS" >> C:\Logs\1703-Upgrade.log

# Update Wallpaper
Set-FileOwnership -File "C:\windows\web\Wallpaper\Windows\img0.jpg" -User Users
Set-FilePermissions -File "C:\windows\web\Wallpaper\Windows\img0.jpg" -User Users -Control FullControl -Access Allow
Copy-Item "C:\Windows.old\windows\Web\Wallpaper\Windows\img0.jpg" -Destination "C:\windows\web\Wallpaper\Windows\img0.jpg" -Force -ErrorVariable +ProcessError
Write-Log -Passed "Copied C:\windows\web\Wallpaper\Windows\img0.jpg to C:\windows\web\Wallpaper\Windows\img0.jpg" -Failed "Failed to copy C:\windows\web\Wallpaper\Windows\img0.jpg to C:\windows\web\Wallpaper\Windows\img0.jpg"

# Update 4k Wallpapers
$Wallpapers = Get-ChildItem C:\Windows\Web\4K\Wallpaper\Windows
ForEach($Wallpaper in $Wallpapers) {
    Set-FileOwnership -File $Wallpaper.FullName -User Users
    Set-FilePermissions -File $Wallpaper.FullName -User Users -Control FullControl -Access Allow

    $FileName = $Wallpaper.Name
    $FilePath = $Wallpaper.FullName

    Copy-Item C:\Windows.old\windows\Web\4K\Wallpaper\Windows\$FileName -Destination $Wallpaper.FullName -Force -ErrorVariable +ProcessError
    Write-Log -Passed "Copied C:\Windows.old\windows\Web\4K\Wallpaper\Windows\$FileName to $FilePath" -Failed "Failed to copy C:\Windows.old\windows\Web\4K\Wallpaper\Windows\$FileName to $FilePath"
}   

# Uninstall OneDrive
Write-Output " " >> C:\Logs\1703-Upgrade.log
Write-Output "$(Get-TimeStamp) UNINSTALLING ONEDRIVE" >> C:\Logs\1703-Upgrade.log
Start-Process C:\Windows\SysWOW64\OneDriveSetup.exe /uninstall -Wait -ErrorVariable +ProcessError
Write-Log -Passed "Uninstalled OneDrive successfully" -Failed "Failed to uninstall OneDrive"

# Rename OneDriveSetup.exe (This is to prevent OneDrive First Run)
Write-Output " " >> C:\Logs\1703-Upgrade.log
Write-Output "$(Get-TimeStamp) RENAMING ONEDRIVESETUP.EXE TO PREVENT ONEDRIVE FROM RUNNING AT LOGON" >> C:\Logs\1703-Upgrade.log
Set-FileOwnership -File C:\Windows\SysWOW64\OneDriveSetup.exe -User Users
Set-FilePermissions -File C:\Windows\SysWOW64\OneDriveSetup.exe -User Users -Control FullControl -Access Allow
Rename-Item C:\Windows\SysWOW64\OneDriveSetup.exe C:\Windows\SysWOW64\OneDriveSetup.exe.old -ErrorVariable +ProcessError
Write-Log -Passed "Renamed C:\Windows\SysWOW64\OneDriveSetup.exe to C:\Windows\SysWOW64\OneDriveSetup.exe.old" -Failed "Failed to rename C:\Windows\SysWOW64\OneDriveSetup.exe to C:\Windows\SysWOW64\OneDriveSetup.exe.old"

# Remove OneDrive run key
Write-Output " " >> C:\Logs\1703-Upgrade.log
Write-Output "$(Get-TimeStamp) REMOVING ONEDRIVE RUN KEY FROM THE DEFAULT'S NTUSER.DAT FILE" >> C:\Logs\1703-Upgrade.log
cmd /c REG LOAD "HKLM\DEFAULT_USER" "C:\Users\Default\NTUSER.DAT" 
Remove-ItemProperty -Path "HKLM:\DEFAULT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "OneDriveSetup" -ErrorVariable +ProcessError
Write-Log -Passed "Removed OneDriveSetup from the default profile's run key" -Failed "Failed to remove OneDriveSetup from the default profile's run key"
cmd /c REG UNLOAD "HKLM\DEFAULT_USER"

# Delete run key for Windows Defender
Write-Output " " >> C:\Logs\1703-Upgrade.log
Write-Output "$(Get-TimeStamp) REMOVING WINDOWS DEFENDER FROM THE CURRENT USER RUN KEY" >> C:\Logs\1703-Upgrade.log
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "SecurityHealth" -ErrorVariable +ProcessError
Write-Log -Passed "Removed SecurityHealth from the current user's run key" -Failed "Failed to remove SecurityHealth from the current user's run key"

# Remove Contact Support
Write-Output " " >> C:\Logs\1703-Upgrade.log
Write-Output "$(Get-TimeStamp) REMOVING THE CONTACT SUPPORT APPLICATION" >> C:\Logs\1703-Upgrade.log
Get-WindowsCapability -online | ? {$_.Name -like ‘*ContactSupport*’} | Remove-WindowsCapability –online -ErrorVariable +ProcessError
Write-Log -Passed "Removed the Contact Support application" -Failed "Failed to remove the Contact Support application"

# Move Office 2016 Applications to correct folder in the Start Menu if needed
Write-Output " " >> C:\Logs\1703-Upgrade.log
Write-Output "$(Get-TimeStamp) MOVING OFFICE 2016 APPLICATIONS TO CORRECT FOLDER IN START MENU" >> C:\Logs\1703-Upgrade.log
$OfficePrograms = GCI "C:\ProgramData\Microsoft\Windows\Start Menu\Programs" | Where-Object name -like "*2016.lnk"

If($OfficePrograms.count -gt 0) {
    ForEach($Program in $OfficePrograms) {
    $OfficeFilePath = $Program.FullName
    $OfficeFileName = $Program.Name
    Copy-Item "$OfficeFilePath" -Destination "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft Office 2016\$OfficeFileName"  -Force -ErrorVariable +ProcessError
    Remove-Item "$OfficeFilePath"
    Write-Log -Passed "Copied $OfficeFilePath to C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft Office 2016\$OfficeFileName" -Failed "Failed to copy $OfficeFilePath to C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft Office 2016\$OfficeFileName"

    }
}
Else {
    Write-Output "$(Get-TimeStamp) Did not find any Office 2016 programs outside of C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft Office 2016" >> C:\Logs\1703-Upgrade.log
}


# Remove 1703 apps
Write-Output " " >> C:\Logs\1703-Upgrade.log
Write-Output "$(Get-TimeStamp) PREPARING TO REMOVE APPS" >> C:\Logs\1703-Upgrade.log
$AppsList = "Microsoft.WindowsFeedbackHub", "Microsoft.XboxIdentityProvider", "Microsoft.Windows.HolographicFirstRun", "Windows.ContactSupport", "Microsoft.XboxGameCallableUI", "HoloShell", "HoloItemPlayerApp", "HoloCamera", "Microsoft.OneConnect", "Microsoft.People", "Microsoft.XboxSpeechToTextOverlay", "Microsoft.XboxGameOverlay", "Microsoft.SkypeApp", "Microsoft.MicrosoftSolitaireCollection", "Microsoft.MicrosoftOfficeHub", "Microsoft.3DBuilder", "Microsoft.Getstarted", "Microsoft.Microsoft3DViewer", "Microsoft.Office.OneNote", "Microsoft.XboxApp", "Microsoft.ZuneMusic", "Microsoft.ZuneVideo", "Microsoft.MSPaint"
ForEach ($App in $AppsList)
{
  $PackageFullName = (Get-AppxPackage $App).PackageFullName
  $ProPackageFullName = (Get-AppxProvisionedPackage -online | where {$_.Displayname -eq $App}).PackageName
  Write-Host $PackageFullName
  Write-Host $ProPackageFullName

  If ($PackageFullName)
  {
    Write-Host “Removing Package: $App”
    Remove-AppxPackage -package $PackageFullName -ErrorVariable +ProcessError
    Write-Log -Passed "Removed $App" -Failed "Failed to remove $App"

  }
  else
  {
    Write-Output "$(Get-TimeStamp) Unable to find package: $App” >> C:\Logs\1703-Upgrade.log
  }

  if ($ProPackageFullName)
  {
    Write-Host “Removing Provisioned Package: $ProPackageFullName”
    Remove-AppxProvisionedPackage -online -packagename $ProPackageFullName -ErrorVariable +ProcessError
    Write-Log -Passed "Removed $ProPackageFullName" -Failed "Failed to remove $ProPackageFullName"
  }
  else
  {
    Write-Output "$(Get-TimeStamp) Unable to find provisioned package: $App” >> C:\Logs\1703-Upgrade.log
  }
}
$ErrorCount = $Errors
Write-Output " " >> C:\Logs\1703-Upgrade.log
Write-Output "$(Get-TimeStamp) CLEANUP COMPLETE - FOUND $ErrorCount ERRORS" >> C:\Logs\1703-Upgrade.log

Set-FileOwnership and Set-FilePermissions Powershell function

Here are two separate Powershell functions that will configure file ownership and file permissions. I had to create them, since I was having to reuse the same code multiple times in my upcoming Creators Update 1703 cleanup script. Enjoy!

Set-FileOwnership

Function Set-FileOwnership {
    <#
    .SYNOPSIS
    Sets File Ownership
 
    .PARAMETER File
    Provide file path
 
    .PARAMETER User
    Provide a username or group that requires file ownership
 
    .EXAMPLE
    Set-FileOwnership -File "C:\windows\web\Wallpaper\Windows\img0.jpg" -User Users
 
    .EXAMPLE
    Set-FileOwnership -File "C:\windows\web\Wallpaper\Windows\img0.jpg" -User Administrators
    #>
    param (
     [parameter(Mandatory=$true)]
     [ValidateNotNullOrEmpty()]$File,
     [parameter(Mandatory=$true)]
     [ValidateNotNullOrEmpty()]$User
    )

    $ACL = Get-ACL "$File"
    $Group = New-Object System.Security.Principal.NTAccount("$User")
    $ACL.SetOwner($Group)
    Set-Acl -Path "$File" -AclObject $ACL

}

Set-FilePermissions

Function Set-FilePermissions {
    <#
    .SYNOPSIS
    Sets File Permissions
 
    .PARAMETER File
    Provide file path
 
    .PARAMETER User
    Provide a username or group that requires permissions configured

    .PARAMETER Control
    Provide file system rights (Ex: FullControl, Modify, ReadAndExecute, etc)
 
    .PARAMETER Access
    Provide file system access rule (Ex: Allow or Deny)
 
    .EXAMPLE
    Set-FilePermissions -File "C:\windows\web\Wallpaper\Windows\img0.jpg" -User Users -Control FullControl -Access Allow
 
    .EXAMPLE
    Set-FilePermissions -File "C:\windows\web\Wallpaper\Windows\img0.jpg" -User Administrators -Control ReadAndExecute -Access Allow
    #>
    param (
     [parameter(Mandatory=$true)]
     [ValidateNotNullOrEmpty()]$File,
     [parameter(Mandatory=$true)]
     [ValidateNotNullOrEmpty()]$User,
     [parameter(Mandatory=$true)]
     [ValidateNotNullOrEmpty()]$Control,
     [parameter(Mandatory=$true)]
     [ValidateNotNullOrEmpty()]$Access
    )

    $ACL = Get-ACL "$File"
    Set-Acl -Path "$File" -AclObject $ACL
    $Permission = New-Object  system.security.accesscontrol.filesystemaccessrule("$User","$Control","$Access")
    $Acl.SetAccessRule($Permission)
    Set-Acl -Path "$File" -AclObject $ACL

}