Powershell

Get-CMGStatus Function

This small Powershell function can be used to determine if an SCCM client is connected to the Internet or Intranet. It is useful when you have a job that requires some type of network resource and it cannot run while your client is connected to a cloud management gateway. Using this example, you can easily determine the connection with the function and then decide to exit the script and run it at a later time.

Function Get-CMGStatus {

    <#
  
    .SYNOPSIS
    Queries SCCM connection type to determine if the PC is connected to a CMG
  
    #>

    $ClientInfo = Get-WmiObject -namespace root\ccm -Class ClientInfo

    If($ClientInfo.InInternet) {

        Return $True

    }
    Else {
        
        Return $False

    }

}

Install-Font Function

Use the Install-Font function to install system fonts on Windows 10 1809 and above. Older scripts may not work with Windows 10 1809 and above since Windows will now try to install fonts in the user’s LOCALAPPDATA directory. This function will get around those issues and allow you to programmatically install fonts for all users again.

How to use the function:

Install Fonts from folder
Install-Font “C:\Temp\Helvetica Neue”

Install one font
Install-Font “C:\Temp\Helvetica Neue\HelveticaNeueLTStd-HvIt.otf”


Function Install-Font {

    <#  
 
    .SYNOPSIS Install system fonts for all users
 
    .PARAMETER FontPath Provide path to a font or a folder containing fonts

    .PARAMETER Recurse Scan subdirectories
 
    .EXAMPLE - Install Fonts from folder
    Install-Font "C:\Temp\Helvetica Neue"
 
    .EXAMPLE - Install one font 
    Install-Font "C:\Temp\Helvetica Neue\HelveticaNeueLTStd-HvIt.otf"
 
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [String]$FontPath,
        [Switch]$Recurse
    )

    If(Test-Path $FontPath) {
        
        $FontFile = Get-Item -Path $FontPath

        If($FontFile -is [System.IO.DirectoryInfo]) {

            If($Recurse) {

                $Fonts = Get-ChildItem -Path $FontFile -Include ('*.fon','*.otf','*.ttc','*.ttf') -Recurse

            }
            Else {

                $Fonts = Get-ChildItem -Path "$FontFile\*" -Include ('*.fon','*.otf','*.ttc','*.ttf')

            }
            If(!$Fonts) {

                Throw ("Unable to find any fonts in the folder")

            }

        }
        ElseIf($FontFile -is [IO.FileInfo]) {

            If ($FontFile.Extension -notin ('.fon','.otf','.ttc','.ttf')) {

                Throw ("The file provided does not appear to be a valid font")

            }

            $Fonts = $FontFile

        }
        Else {
        
            Throw ("Expected font or folder")
        
        }

    }
    Else {

        Throw [System.IO.FileNotFoundException]::New("Could not find path: $FontPath")

    }
    ForEach ($Font in $Fonts) {

        $FontName = $Font.Basename
        Write-Host "Installing font: $FontName"
        Copy-Item $Font "C:\Windows\Fonts" -Force
        New-ItemProperty -Name $FontName -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Fonts" -PropertyType String -Value $Font.Name -Force | Out-Null

    }

}

Change your Office 365 ProPlus Update Channel using SCCM

Office 365 ProPlus has four different update channels:

  • Monthly Channel (Targeted)
  • Monthly Channel
  • Semi-annual Channel (Targeted)
  • Semi-annual Channel

In order to switch your client to different channels, you must change the CDNBaseUrl and UpdateChannel registry keys in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\Configuration. In the Set-Office365Channel function we will be using C:\Program Files\Common Files\Microsoft Shared\ClickToRun\OfficeC2RClient.exe to update the CDNBaseUrl key and we will be automatically setting the UpdateChannel key. We are updating the UpdateChannel key because in my testing, SCCM would not deploy the new update channel patches if you did not set this key. After these two tasks are complete, the function will run a hardware inventory scan so SCCM can update the channel in the console.

How to use Set-Office365Channel:

  • Switch to Monthly Channel (Targeted)
    Set-Office365Channel -UpdateChannel Insiders
  • Switch to Monthly Channel
    Set-Office365Channel -UpdateChannel Monthly
  • Switch to Semi-annual Channel (Targeted)
    Set-Office365Channel -UpdateChannel Targeted
  • Switch to Semi-annual Channel
    Set-Office365Channel -UpdateChannel Broad
Function Set-Office365Channel {

    <#  

    .SYNOPSIS Change Office 365 Update Channel 

    .PARAMETER UpdateChannel Provide update channel that you would like to use 

    .EXAMPLE - Switch to Monthly Channel (Targeted) 
    Set-Office365Channel -UpdateChannel Insiders

    .EXAMPLE - Switch to Monthly Channel 
    Set-Office365Channel -UpdateChannel Monthly

    .EXAMPLE - Semi-annual Channel (Targeted) 
    Set-Office365Channel -UpdateChannel Targeted

    .EXAMPLE - Semi-annual Channel 
    Set-Office365Channel -UpdateChannel Broad

    #>

    param (
        # Provide update channel that you would like to use
        [parameter(Mandatory=$True)]
        [ValidateSet('Insiders', 'Monthly', 'Targeted', 'Broad')]
        [string]$UpdateChannel
    )

    $Channel = Switch ($UpdateChannel) {

        "Insiders" {"http://officecdn.microsoft.com/pr/64256afe-f5d9-4f86-8936-8840a6a4f5be"}
        "Monthly" {"http://officecdn.microsoft.com/pr/492350f6-3a01-4f97-b9c0-c7c6ddf67d60"}
        "Targeted" {"http://officecdn.microsoft.com/pr/b8f9b850-328d-4355-9145-c59439a0c4cf"}
        "Broad" {"http://officecdn.microsoft.com/pr/7ffbc6bf-bc32-4f92-8982-f9dd17fd3114"}

    }

    $Configuration = "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration"

    If((Get-ItemProperty $Configuration).CDNBaseUrl -ne "$Channel") {

        # Update Channel
        Start-Process "C:\Program Files\Common Files\Microsoft Shared\ClickToRun\OfficeC2RClient.exe" -ArgumentList "/changesetting Channel=$UpdateChannel"
        Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" -Name UpdateChannel -Value "$Channel" -Force

        # Trigger Hardware Inventory
        Invoke-WmiMethod -Namespace "root\ccm" -Class "SMS_Client" -Name "TriggerSchedule" -ArgumentList "{00000000-0000-0000-0000-000000000001}"

    }

}

How to reset your start menu layout in Windows 10 1809

Well Microsoft has changed things again since my last post that showed you how to reset the start layout in Windows 10 1709. Now with 1809 there is a new key name and it does look to be slightly random so I am now having to use a wildcard. I’m currently only testing every other build so please keep me updated if this breaks with a spring feature upgrade release.

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

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

SCCM script to identify systems vulnerable to ADV180028

You can run the following script against an SCCM collection to identify a system’s Bitlocker encryption method. This will help you find any computers that may be vulnerable to ADV180028.

Note: Your system may be vulnerable if your encryption method is set to Hardware Encryption!

$EncryptionMethod = manage-bde -status C: | Where-Object {$_ -match "Encryption Method"}

If ($EncryptionMethod -ne $Null) {

    $EncryptionMethod = $EncryptionMethod.Split(":")[1].trim()

}
Else {

    $EncryptionMethod = "Encryption Method not found"

}

$EncryptionMethod

Learn more about ADV180028 here.
Learn how to deploy scripts in SCCM here.

Get-LoggedOnUser Function

Use the Get-LoggedOnUser function to find out who is the current logged on user on a local or remote machine. The function can be useful for SCCM deployments or just trying to find out who is logged on a remote computer.

Examples:
Find out who is logged on to a remote machine:
Get-LoggedOnUser -ComputerName COMPUTERNAME-D
Find out the current local logged on user:
Get-LoggedOnUser

Source Code:

Function Get-LoggedOnUser {

    <#
 
    .SYNOPSIS
    Find out the current logged on user on a local or remote machine
   
    .PARAMETER ComputerName
    Provide remote computer name
   
    .EXAMPLE
    Get-LoggedOnUser
   
    .EXAMPLE
    Get-LoggedOnUser -ComputerName COMPUTERNAME-D
 
    #>

    param (
     [parameter(Mandatory=$False)]
     [ValidateNotNullOrEmpty()]$ComputerName
    )
    
    If($ComputerName -eq $Null) {

        $Username = (Get-Process Explorer -IncludeUsername | Where-Object { $_.Username -notlike "*SYSTEM" }).Username

    }
    Else {

        $Username = (Invoke-Command {Get-Process Explorer -IncludeUsername | Where-Object { $_.Username -notlike "*SYSTEM" }} -ComputerName $ComputerName).Username

    }

    Return $Username

}

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

        }

    }

}