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