Wednesday, November 17, 2021

DISM Servicing Script


This was born of my need to remove OneDrive from annoying everyone into installing it.  My client will soon be turning on OneDrive for Business, so the default OneDrive needed to be removed from the image.  I also found that I couldn't install one of their pieces of financial software because .NET 3.5 was not enabled and, though I could just enable it with a one-liner in the task sequence, I thought it would be nice if their image just had that turned on.  I also thought that it would be nice to have CMTrace.exe available on every PC in my environment.

Finally (and the part the prompted me into actually scripting the whole thing) I found it annoyingly difficult to change the default user's theme, including the lock screen, without a GPO.  The issue is that Trusted Installer service protects the area where the themes are stored, so if the machine is online you have to take ownership of the files which subverts the Trusted Installer.  I wanted to have my theme work but also have Trusted Installer work.  You can do this with SysPrep but I decided to do some offline manipulation of the image because I wanted to practice my "DISM-FU".

So, here it is.  The script mounts the WIM, enables .NET 3.5, copies in the wallpaper and .theme files for the theme, sets a registry item in HKU\.default's RunOnce to run the theme thus setting it active on a user's first logon, sets the lock screen registry items, and finally rips out OneDriveSetup.exe itself and the call to run it from HKU\.default.

SourceWIM = the location of your WIM file that you want to edit
FeaturesSource = the "sources" folder from your original Windows 10 media
WallpaperSource = the network location where you have stored the wallpaper you want to use
LockScreenSource = the file name within WallpaperSource to use as the lock screen
CMTraceSource = network location of CMTrace to copy into the WIM
LogFile = Where you would like this script to spit out its log file

I often comment out the unmount line at the end so that I can make adjustments if I want to after the .bat file runs.

Here it is, enjoy

MD C:\DISMTemp

DISM /Mount-wim /WimFile:"\\server.domain.com\OperatingSystemDeployment\OS Images\Win10v20H2\Win10v20H2\Win10v20H2.WIM" /index:1 /MountDir:C:\DISMTemp

DISM /Image:C:\DISMTemp /Enable-Feature /FeatureName:NetFx3 /All /LimitAccess /Source:"\\server.domain.com\OperatingSystemDeployment\OS Images\Win10v20H2\sources"


COPY "\\server\ApplicationManagement\_Scripts\ManageTheme\Company*.themepack" "C:\DISMTemp\Windows\SysWOW64\Resources\Themes\*.*"

COPY "\\server\ApplicationManagement\_Scripts\ManageTheme\LockScreen_Company.jpg" "C:\DISMTemp\Windows\SysWOW64\Resources\Themes\*.*"

COPY "\\server\ClientTools\ConfigMgr 2012 Toolkit R2\ClientTools\CMTrace.exe" "C:\DISMTemp\Windows\System32"


REG.EXE load HKLM\DISMHKLMSoftware C:\DISMTemp\Windows\System32\config\software

REG.EXE load HKLM\DISMHKUD C:\DISMTemp\Windows\System32\config\DEFAULT


REG.EXE add "HKLM\DISMHKLMSoftware\Microsoft\Windows\CurrentVersion\Themes" /v "InstallTheme" /t REG_SZ /d "%systemroot%\Resources\Themes\Company.themepack" /f

REG.EXE add "HKLM\DISMHKLMSoftware\Policies\Microsoft\Windows\Personalization" /v “LockScreenImage” /t REG_SZ /d ”%systemroot%\Resources\Themes\Lockscreen_Company.jpg” /f

REG.EXE add "HKLM\DISMHKLMSoftware\WOW6432Node\Microsoft\Windows\CurrentVersion\Themes" /v "InstallTheme" /t REG_SZ /d "%systemroot%\Resources\Themes\Company.themepack" /f

REG.EXE add "HKLM\DISMHKLMSoftware\WOW6432Node\Microsoft\Windows\Personalization" /v “LockScreenImage” /t REG_SZ /d ”%systemroot%\Resources\Themes\Lockscreen_Company.jpg” /f


REG.EXE add "HKLM\DISMHKUD\Software\Microsoft\Windows\CurrentVersion\Themes" /v "InstallTheme" /t REG_EXPAND_SZ /d "%systemroot%\Resources\Themes\Company.themepack" /f


REG.EXE unload HKLM\DISMHKLMSoftware

REG.EXE unload HKLM\DISMHKUD


REM DISM /UnMount-WIM /MountDir:C:\DISMTemp /discard

REM DISM /UnMount-WIM /MountDir:C:\DISMTemp /commit 

Extract Specific Edition Wim from Multiple Edition Wim

 Extract Specific Edition Wim from Multiple Edition Wim

  1. Mount the ISO, locate the drive and navigate to find the location of install.wim (drive:\sources\install.wim)

  2. Open PowerShell with elevated rights. Run the following command:

    Get-WindowsImage -ImagePath <drive:>\sources\install.wim

  3. We can see existing install.wim consists of following 10 Windows 10 versions:
    Windows 10 Education
    Windows 10 Education N
    Windows 10 Enterprise
    Windows 10 Enterprise N
    Windows 10 Pro
    Windows 10 Pro N
    Windows 10 Pro Education
    Windows 10 Pro Education N
    Windows 10 Pro for Workstations
    Windows 10 Pro N for Workstations

  4. Run following command to extract enterprise wim from multiple edition wim :

    Export-WindowsImage -SourceImagePath <drive:\sources\install.wim> -DestinationImagePath <drive:\folder\FileName.wim> -SourceIndex <#>


    Where -SourceImagePath is the location of original install.wim, -DestinationImagePath is where you want to save the new wim, -SourceIndex is the index number of Windows 10 version, in this case it is Enterprise edition.

    Example: Export-WindowsImage -SourceImagePath F:\sources\install.wim -DestinationImagePath C:\Win10_20H2\20H2Ent.wim -SourceIndex 3

Friday, June 18, 2021

SCOM Gateway Certificates

It took me forever to put together this definitive step-by-step on how to create the SCOM Gateway Certificates template.  There is a lot out there about the fact that you need them, how to install them, etc.  But when you need to give someone instructions on how to create the template that information is hard to come by.  These instructions are for a Microsoft CA, obviously if you are using a different Certificate Authority then it will vary but this should still have the essentials that you need.  Enjoy...

On the CA,

1. Select "Manage" under Certificate Templates

2. Select the template "Ipsec Offline request" and select duplicate template

○ Compatibility tab 

§ Leave defaults

○ General Tab

§ Set an appropriate Name (like SCOM Gateway Certrificate Template)

§ Set Validity to 2 years

§ Set Renewal period to 6 weeks

○ Request Handling tab 

§ check Allow Private Key to be exported (this is essential and very important as you will be exporting these to import on the management servers)

○ Cryptography tab

§ select "Providers Microsoft RSA SChannel Cryptographic Provider"

§ Select "Microsoft Enhanced Cryptographic Provider v 1.0"

○ Extensions tab

§ select Application Policies click edit 

§ select Client Authentication and Server Authentication

○ Security tab

§ Under Groups or user names click "Add"

§ Select the object type of "Computer"

§ Search for the management server names

§ Grant Read and Enroll permissions to the Management Servers.  This allows the management servers to auto-renew, you will still have to manually renew the gateways.  Depending upon your AD trust relationships you may be able to add the gateways as well and then they would also be able to auto-renew.  However if that sort of trust exists then you likely did not need the gateway in the first place.  But, point is, there is no harm trying to add the gateways and it may end up helping you.


Go back to the Certificate Authority Console

3. Right-click Certificate Template, select New Certificate Template to Issue

• Select the new template created in step 2


On the management server

1. Launch https://ad/certsrv (https://adservername/Certsrv) from Management Server and select Advanced certificate request

2. Select the certificate template created in step 2.

Certreq inf settings:

[NewRequest]

Subject="CN=<YOUR SERVER FQDN>"

Exportable=TRUE

KeyLength=2048

KeySpec=1

KeyUsage=0xf0

MachineKeySet=TRUE

[EnhancedKeyUsageExtension]

OID=1.3.6.1.5.5.7.3.1

OID=1.3.6.1.5.5.7.3.2




Monday, January 25, 2021

Change Windows Updates Server on Network Location Change

I had a client that had implemented a new VPN solution that uses Network Access Control to have the client check Windows Updates and report to the VPN server how many updates it is missing.  If the client is missing too many updates then the client was not allowed on VPN.

This sounds great until WSUS is involved.  With WSUS the computer points to a server inside of the network boundary to get its updates, so when the VPN said "go check the client could not get to its update server and therefore could not get on VPN.

The client did have a cloud management gateway so if the clients were pointed to the CMG they could connect, however when on VPN they cannot communicate with the CMG and therefore cannot get updates.  This leads to a catch-22... either they cannot VPN or they cannot get updates.  Both are critical.  So, here's what I came up with.

  1. Use a Scheduled Task that triggers on any network connection being established or disconnected (both connect and disconnect must be checked).  
    • When the trigger occurs, check the existing network connections to determine if we are inside of the network boundary or if we are completely out on the internet and change the Windows Updates server accordingly.
  2. Use a PowerShell script to do the checks and make the changes mentioned in #1 (the action for the Scheduled Task).
  3. Use an SCCM Configuration Item to ensure that all of the computers have the PowerShell script and the Scheduled Task.
    • The configuration item should be scripted to install the Scheduled Task and Powershell script if they do not exist.

So here we go:

The scheduled task is built from this .xml:

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Description>Checks settings whenever a network connection is started to ensures that the Windows Update server is correctly updated for internal (on premises or VPN) network or external (internet) network.</Description>
    <URI>\Change Windows Updates Server on Network Change</URI>
  </RegistrationInfo>
  <Triggers>
    <EventTrigger>
      <ExecutionTimeLimit>PT30M</ExecutionTimeLimit>
      <Enabled>true</Enabled>
      <Subscription>&lt;QueryList&gt;&lt;Query Id="0" Path="Microsoft-Windows-NetworkProfile/Operational"&gt;&lt;Select Path="Microsoft-Windows-NetworkProfile/Operational"&gt;*[System[Provider[@Name='Microsoft-Windows-NetworkProfile'] and EventID=10001]]&lt;/Select&gt;&lt;/Query&gt;&lt;/QueryList&gt;</Subscription>
    </EventTrigger>
    <EventTrigger>
      <ExecutionTimeLimit>PT30M</ExecutionTimeLimit>
      <Enabled>true</Enabled>
      <Subscription><QueryList><Query Id="0" Path="Microsoft-Windows-NetworkProfile/Operational"><Select Path="Microsoft-Windows-NetworkProfile/Operational">*[System[Provider[@Name='Microsoft-Windows-NetworkProfile'] and EventID=10001]]</Select></Query></QueryList></Subscription>
    </EventTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">
      <UserId>S-1-5-18</UserId>
      <RunLevel>HighestAvailable</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>Queue</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
    <AllowHardTerminate>true</AllowHardTerminate>
    <StartWhenAvailable>false</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>false</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
    <UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>powershell.exe</Command>
      <Arguments>-executionpolicy bypass -file "C:\Windows\CCMCache\Check-WUServerSetting.ps1"</Arguments>
    </Exec>
  </Actions>
</Task>


 The action for the scheduled task is to run this PowerShell script (Check-WUServerSettings.ps1) :

#
.Synopsis
   Check-WUServerSettings.ps1 checks the registry items for the Windows Update Server
.DESCRIPTION
   Checks the registry items for the Windows Update Server to be sure they are set correctly
   for internal/external networks.  This was created to ensure that the settings switched when
   changing between VPN and off VPN but may have other uses.  This was necessitated by an F5
   VPN with Network Access Controls that checked software updates, if the computer could not
   connect to the software updates server at the time of that check then the user could not
   connect to VPN.
.INPUTS
   There are no command line inputs.  However $InternalServer and $ExternalServer need to be
   modified to represent the correct values for your organization
.OUTPUTS
   If the script detects that the registry values are correct it will return the registry value
   otherwise it returns nothing (so that it can be used as a detection script in SCCM).
.NOTES
   Written 01/19/2021 by Mark Randol - randoltech.blogspot.com
#>


$InternalServer = "http://WSUSServer.YourCompany.com:8530"
$ExternalServer = "https://CMGServer.YourCompany.COM/CCM_Proxy_ServerAuth/12345678901234567"
$IsCompliant = $false

if (get-item "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate") {
    $WUServerValue = (Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate").WUServer
    $WUStatusServerValue = (Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate").WUStatusServer
}

if (Get-NetIPConfiguration | Where-Object { $_.NetAdapter.Status -ne "Disconnected" -and ($_.NetProfile.Name -eq "YourCompany.com") }) { #check settings for internal servers
    $InsideBoundary = $true
    if ($WUServerValue -ne $InternalServer -or $WUStatusServerValue -ne $InternalServer ) { #change the server values if any are wrong for internal
        Set-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name WUServer -Value $InternalServer
        Set-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name WUStatusServer -Value $InternalServer
    }   
    #Check the values again to be sure that they got set  
    $WUServerValue = (Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate").WUServer
    $WUStatusServerValue = (Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate").WUStatusServer
    if ($WUServerValue -eq $InternalServer -and $WUStatusServerValue -ne $InternalServer ) {$IsCompliant = $true }
} # check settings for internal servers
else { #check settings for external servers
    $InsideBoundary = $false
    if ($WUServerValue -ne $ExternalServer -or $WUStatusServerValue -ne $ExternalServer  ) { #change the server values if any are wrong for external
        Set-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name WUServer -Value $ExternalServer
        Set-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name WUStatusServer -Value $ExternalServer
    }
    #Check the values again to be sure that they got set  
    $WUServerValue = (Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate").WUServer
    $WUStatusServerValue = (Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate").WUStatusServer
    if ($WUServerValue -eq $ExternalServer -and $WUStatusServerValue -ne $ExternalServer ) {$IsCompliant = $true }
} # check settings for external servers

if ($IsCompliant = $true) {
    $OutputString = "Windows Update Server = $WUServerValue"
#    Write-Output $OutputString
    Exit $OutputString
}
else {
}


To install the PowerShell script I used this batch file (InstallCheck-WUServerSetting.bat):

copy Check-WUServerSetting.ps1 C:\Windows\CCMCache\Check-WUServerSetting.ps1 /Y
powershell.exe -executionpolicy bypass -file .\InstallCheck-WUServerSetting.ps1


And the PowerShell script called by the batch file (InstallCheck-WUServerSetting.ps) contains just this one line (yes, I could have simplified and put the 1 liner into the .bat but that would entail messing around with the quotation marks for an hour or so to get them right):

Register-ScheduledTask -Xml (get-content 'Change Windows Updates Server on Network Change.xml' | out-string) -TaskName "Change Windows Updates Server on Network Change"


Once we have all of that, we create an application in SCCM containing the four files.  We create a deployment method of the .bat file and a detection method of checking for existence of "C:\Windows\CCMCache\Check-WUServerSetting.ps1".

End result is that every time you connect/disconnect a network the script "C:\Windows\CCMCache\Check-WUServerSetting.ps1" gets called by the Scheduled Task and sets the registry settings for your Windows Updates servers correctly.

I put the scheduled task's action script into CCMCache because that is a protected folder to which the user should not generally have access.

Thursday, January 14, 2021

Powershell - Check if computer is On-Premise, on VPN, or outside on the Public Internet

Powershell - Check if computer is on premises (or VPN) or outside on the Internet

If, when you run the following command, your domain name appears in the list then your computer is inside of your network boundary (could be physically inside or on VPN).

(Get-NetIPConfiguration | Where-Object { $_.NetAdapter.Status -ne "Disconnected" }).NetProfile.Name

So, expanding on that command, here's a script that you can use in something like an SCCM configuration item to check it:
<#
.Synopsis
   Check-NetBoundary checks if the computer is inside the network, on VPN, or outside on the internet
.DESCRIPTION
   Returns "Public", "On-Premises", or "VPN" depending on results.
   Check-NetBoundary checks if the computer is inside the network or outside on the internet
   This was originally written to determine if computers were connected on VPN or not but may have other uses.
   Change the variable $CompanyNetProfileName to equal your own domain name (or the name given to your network connection profile if it is different than your domain).
   Written 01/14/2021 by Mark Randol - randoltech.blogspot.com
#>


CLS
$CompanyNetProfileName = "mydomain.com"
$Location = "Public"
$InsideBoundary = $false
if (Get-NetIPConfiguration | Where-Object { $_.NetAdapter.Status -ne "Disconnected" -and $_.NetProfile.Name -eq $CompanyNetProfileName}) {
    $Location = "On-premises"
    if (Get-NetIPConfiguration | Where-Object { $_.NetAdapter.Status -ne "Disconnected" -and $_.NetProfile.Name -ne $CompanyNetProfileName}) {
        $Location = "VPN"
    }
}
Write-Output $Location