Fix Citrix Receiver DPI issues

Have you tried running a Citrix published application with Windows 10 after modifying your DPI settings? Well, you may have notice that your application is scaled incorrectly and your cursor is a little off. If you have a user that MUST use a higher DPI setting, then you can run the following command on the user’s PC to fix the issue:

reg add "HKCU\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers" /v "C:\Program Files (x86)\Citrix\ICA Client\wfica32.exe" /t REG_SZ /d "~ WIN7RTM" /f

This command will automatically set the compatibility settings for “C:\Program Files (x86)\Citrix\ICA Client\wfica32.exe” and it will configure the EXE to run in compatibility mode for Windows 7.

However if you do not need to scale your published application, then you can run the following command to disable display scaling on high DPI settings for “C:\Program Files (x86)\Citrix\ICA Client\wfica32.exe”:

reg add "HKCU\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers" /v "C:\Program Files (x86)\Citrix\ICA Client\wfica32.exe" /t REG_SZ /d "~ HIGHDPIAWARE" /f

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

}

How to run a post script after a Windows 10 feature upgrade with SetupConfig.ini

If you are planning on upgrading your Windows 10 OS from 1607 to 1703 you may have noticed that a few apps have reappeared. Luckily for us, Microsoft has provided a way to add parameters to upgrades with the SetupConfig.ini file.

For example, you can create a Setupconfig.ini with the following:
Note that the header [SetupConfig] is required.

[SetupConfig]
NoReboot
ShowOobe=None
Telemetry=Enable

This is equivalent to the following command line:

Setup.exe /NoReboot /ShowOobe None /Telemetry Enable

How does the upgrade use the SetupConfig.ini file?
If the update is delivered through Windows Update, Windows Setup searches in a default location for a setupconfig file. You can include the setupconfig file here:
“%systemdrive%\Users\Default\AppData\Local\Microsoft\Windows\WSUS\SetupConfig.ini”

How do you run a post script?
You can easily add a post script by adding the PostOOBE parameter to the SetupConfig file as shown below:

[SetupConfig]
PostOOBE=C:\SetupComplete.cmd

How do you run a Powershell script?
The only way that I have been able to run a Powershell script is by running it from the .cmd file that I have called using the PostOOBE parameter.

Inside of the cmd file, you would have to add the following command to launch a Powershell script:

Powershell.exe -ExecutionPolicy Bypass -File "C:\Scripts\Remove-Apps-1703.ps1" -WindowStyle Hidden

I hope this post helps you understand the power of this ini file. Feel free to leave any questions in the comments and stay tuned for the cleanup script that I am currently working on!

UPDATE: The post clean up script can be found here!

How to remove the Contact Support app in Windows 10 1607 and above

The Contact Support app can now officially be removed but the process is a little different than how you would normally remove a Windows app with Powershell. Luckily it isn’t too difficult and it can be done with a one liner!

Get-WindowsCapability -online | ? {$_.Name -like ‘*ContactSupport*’} | Remove-WindowsCapability –online

New laptop chassis type is not recognized by MDT

I recently ran into an issue where MDT could not determine if our new Dell XPS 13 9365 was a laptop or desktop. After running wmic path win32_systemenclosure get chassistypes, I was able to determine that the chassis type 31 was not listed in MDT’s ZTIGather.wsf. Adding 31 to ZTIGather.wsf (Line 417) as shown below, fixed the issue and MDT was now able to determine that the XPS 13 9365 was a laptop!

Example:

Select Case objInstance.ChassisTypes(0)
Case "8", "9", "10", "11", "12", "14", "18", "21", "31"
	bIsLaptop = true
Case "3", "4", "5", "6", "7", "15", "16"
	bIsDesktop = true
Case "23"
	bIsServer = true
Case Else
	' Do nothing
End Select

Feel free to leave any questions in the comment section!

How to silently install LexisNexis InterAction 6.21.21

I recently had some issues specifying server data for my LexisNexis Interaction silent install. For some reason, the server information that I used was not applying during the install. After a few failed attempts, I decided to review the MSIEXEC logs and found the following lines:

Action start 15:13:52: SetDataDirDef.
MSI (s) (F0:68) [15:13:52:542]: Doing action: SetDBServerSRCH
Action ended 15:13:52: SetDataDirDef. Return value 1.
MSI (s) (F0:68) [15:13:52:542]: Transforming table CustomAction.

MSI (s) (F0:68) [15:13:52:542]: PROPERTY CHANGE: Deleting IADBSERVER property. Its current value is ‘SQLSERVER’.

According to the logs, it looks like the server information that I added was getting deleted because of the SetDBServerSRCH action.

Now that I found what was causing the server information to be removed, I decide to review the InterAction MSI table and search for the SetDBServerSRCH action. Once I found the action, I was able to locate the condition that was forcing the action to run.
Condtion:
IANETINSTALL=0 and UILevel<5
Conditions

After a few tests, I verified that you can specify server information if you make the condition false. So all you have to do is add the following to your MSIEXEC command:
IANETINSTALL=2

And for anyone lazy enough to not read everything, here is the entire silent install command for InterAction.
MSIEXEC /I InterAction Desktop Applications.msi IADBSERVER=”DB SERVER” IADBNAME=”DB NAME” IAPORT=”PORT HERE” NETAPPSERVER=”APPSERVER” IANETINSTALL=2 /qn

I hope this helps someone!

“The universal unique identifier (UUID) type is not supported” MDT Fix

During our Windows 10 testing, we noticed that some users would randomly come across the universal unique identifier (UUID) type is not supported error when they logged onto their computer for the first time. In order to get around this error, Microsoft provided a work around that would work with SCCM. Click here for the article.

Unfortunately this does not work well with MDT because the administrator account does not have permission to add a value to the “HKLM\SYSTEM\CurrentControlSet\Services\gpsvc” registry key.
The following Powershell script will fix this by changing the owner of the key to the Administrators group and also providing full access to the Administrators group. This will be temporary since sysprep seems to revert the permissions after it has processed. Fortunately the value stays with the registry key!

Note: In order to have this fix work successfully with MDT, we will need to configure the script to run before the sysprep step in your capture task sequence.

$definition = @"
using System;
using System.Runtime.InteropServices;
 
namespace Win32Api
{
 
public class NtDll
{
[DllImport("ntdll.dll", EntryPoint="RtlAdjustPrivilege")]
public static extern int RtlAdjustPrivilege(ulong Privilege, bool Enable, bool CurrentThread, ref bool Enabled);
}
}
"@
 
Add-Type -TypeDefinition $definition -PassThru
 
$bEnabled = $false
$res = [Win32Api.NtDll]::RtlAdjustPrivilege(9, $true, $false, [ref]$bEnabled)

# Change Owner to the local Administrators group
$regKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey("SYSTEM\CurrentControlSet\Services\gpsvc",[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,[System.Security.AccessControl.RegistryRights]::TakeOwnership)
$regACL = $regKey.GetAccessControl()
$regACL.SetOwner([System.Security.Principal.NTAccount]"Administrators")
$regKey.SetAccessControl($regACL)

# Change Permissions for the local Administrators group
$regKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey("SYSTEM\CurrentControlSet\Services\gpsvc",[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,[System.Security.AccessControl.RegistryRights]::ChangePermissions)
$regACL = $regKey.GetAccessControl()
$regRule = New-Object System.Security.AccessControl.RegistryAccessRule ("Administrators","FullControl","ContainerInherit","None","Allow")
$regACL.SetAccessRule($regRule)
$regKey.SetAccessControl($regACL)

# Add registry key fix
cmd /c reg add "HKLM\SYSTEM\CurrentControlSet\Services\gpsvc" /v Type /t REG_DWORD /d 0x10 /f

Feel free to leave any questions in the comments!

Test-RegValue Function

Powershell has a great CMDLET called Test-Path that can check to see if a registry key exists but unfortunately it does not have the ability to check for registry values. In order to get around this, I created the following function to check to see if a registry value exists. This can be extremely useful when you are using a registry key to verify if a script has already executed in the past.

Examples:
Test-RegValue -Key “HKCU:\Control Panel\Desktop” -Value WallPaper
Test-RegValue -Key “HKLM:\SOFTWARE\Custom” -Value Test

Function Test-RegValue {
    <#
    .SYNOPSIS
    Determine if a registry value exists

    .PARAMETER Key
    Provide registry key path

    .PARAMETER Value
    Provide registry value that you would like to test

    .EXAMPLE
    Test-RegValue -Key "HKCU:\Control Panel\Desktop" -Value WallPaper

    .EXAMPLE
    Test-RegValue -Key "HKLM:\SOFTWARE\Custom" -Value Test
    #>
    param (
     [parameter(Mandatory=$true)]
     [ValidateNotNullOrEmpty()]$Value,
     [parameter(Mandatory=$true)]
     [ValidateNotNullOrEmpty()]$Key
    )

    $ValueExist = (Get-ItemProperty $Key).$Value -ne $null
    Return $ValueExist
}

Read more

Remote Outlook 2016 Caching Report

I was recently asked to figure out a way to determine which users had cache mode enabled in Outlook 2016 and the end result is the script below.

In order to run the script, you will need to specify the following variables:
$Directory = Insert the exact path of where you would like to save your report ( EX: C:\Reports)
$File = Insert the file name of your report (EX: MyReport.csv)
$ComputerList = Insert the full path of your computer list (EX: C:\Computerlist.txt)


<#  

.SYNOPSIS  
    Run a report to find who is in Outlook Cache Mode

.NOTES  
    File Name  : OutlookCachingReport.ps1  
    Author     : Jose Espitia
    Requires   : PowerShell V5
    Version    : Version 1.00

#>

# Specify where to save the report
$Directory = "C:\Reports"
$File = "MyReport.csv"

# Computer list
$ComputerList = "C:\ComputerList"
$Computers = Get-Content "$ComputerList"

ForEach($Computer in $Computers) {
    
    Try { 
        # Test connection with computer
        Test-Connection -ComputerName $Computer -ErrorAction Stop
        # Query remote machines
        $HKEY_Users = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("Users",$Computer)
        # Get list of SIDs
        $SIDs = $HKEY_Users.GetSubKeyNames() | Where-Object { ($_ -like "S-1-5-21*") -and ($_ -notlike "*_Classes") }

        # Associate SID with Username
        $TotalSIDs = ForEach ($SID in $SIDS) {
            Try {
                $SID = [system.security.principal.securityidentIfier]$SID
                $user = $SID.Translate([System.Security.Principal.NTAccount])
                New-Object PSObject -Property @{
                    Name = $User.value
                    SID = $SID.value
                }                 
            } 
            Catch {
                Write-Warning ("Unable to translate {0}.`n{1}" -f $UserName,$_.Exception.Message)
            }
        }
        $UserList = $TotalSIDs 

        # Loop through users to determine If they are in cache mode
        ForEach($User in $UserList) {
            # Get SID
            $UserSID = $User.SID
    
            # Get list of Outlook profiles
            $OutlookProfiles = $HKEY_Users.OpenSubKey("$UserSID\SOFTWARE\Microsoft\Office\16.0\Outlook\Profiles\Outlook\")
    
            # Loop through Outlook profiles to find caching key
            ForEach($Profile in ($OutlookProfiles.GetSubKeyNames())) {
        
                $ProfileKey = $HKEY_Users.OpenSubKey("$UserSID\SOFTWARE\Microsoft\Office\16.0\Outlook\Profiles\Outlook\$Profile")
        
                # Locate cache key
                If(($ProfileKey.GetValueNames() -contains "00036601") -eq $True) {
                    $Result = $ProfileKey.GetValue("00036601")
                    # Convert value to HEX
                    $Result = [System.BitConverter]::ToString($Result)
            
                    # Determine if cache mode is enabled
                    If($Result -like "8*") {
                        $CacheMode = "Enabled"
                    }
                    Else {
                        $CacheMode = "Disabled"
                    }
                    # Create custom table
                    $Table = New-Object PSObject -Property @{
                        Username = $User.Name
                        SID = $User.SID
                        "Computer Name" = $Computer
                        "Cache Mode" = $CacheMode
                        "Registry Key Value" = $Result

                    } | Select-Object Username, SID, "Computer Name", "Cache mode", "Registry Key Value"
                    # Export table to CSV
                    $Table | Export-Csv -NoTypeInformation -Append -Path "$directory\$file"
                }
            
            }
        }
    }
    Catch {
        # Create custom table
        $Table = New-Object PSObject -Property @{
            Username = "N/A"
            SID = "N/A"
            "Computer Name" = $Computer
            "Cache Mode" = "N/A"
            "Registry Key Value" = "N/A"

        } | Select-Object Username, SID, "Computer Name", "Cache mode", "Registry Key Value"
        # Export table to CSV
        $Table | Export-Csv -NoTypeInformation -Append -Path "$directory\$file"
    }
}

Feel free to leave questions in the comments!

Add a wireless profile to your MDT task sequence

First you will need to export the configuration for the wireless profile that you would like to add to your task sequence.
Note: You will need to connect to the wireless profile before you can export it.

:: ADD YOUR WIRELESS PROFILE NAME
SET WIFI-PROFILE="YOUR WIRELESS PROFILE"
:: EXPORT WIRELESS CONFIGURATION
NETSH WLAN EXPORT PROFILE "%WIFI-PROFILE%" FOLDER="%USERPROFILE%\Desktop" KEY=Clear

The export should have copied an XML file to your desktop. In order to keep this simple, go ahead and rename the XML file WirelessProfile.xml. Now, copy WirelessProfile.xml and place it inside of your Deployment Share.
For this example, I will be copying the XML file into a folder called Custom, inside of your scripts folder.

Now go ahead and open up your task sequence and add a “Run Command Line” task inside of the State Restore group.

You can name the task anything you would like but in this example I have named it “Add Wireless Profile”.

Last but not least, you will need to add the following in the Command Line field:

NETSH WLAN ADD PROFILE FILENAME="%SCRIPTROOT%\Custom\WirelessProfile.xml" USER=All

Update (5/2/2018):
There seems to be an issue with running XML files that are not stored locally. If the command to add the wireless does not work, try copying the XML file locally and then running the command.

Now you will have a pre-configured Wireless profile!