Category Archives: Microsoft

  • 0

Automate updating OS ISOs with OSDBuilder and MDT

Now that we’ve started down this “MDT” train, there’s no getting off until we discuss some of the more important parts. If you haven’t read the previous posts you can find them here:

Post 1: https://verticalagetechnologies.com/index.php/2020/03/04/citrix-microsoft-mdt-powershell-tools-the-beginning

Post 2: https://verticalagetechnologies.com/index.php/2020/03/16/building-multiple-citrix-images-with-one-microsoft-mdt-task-sequence

Today we will be focusing on automating the procedures for always having an updated Windows OS ISO. This is especially important for automating master image builds b/c we can’t remove windows updates from the task sequence. Windows Updates can add hours into your build, so if we can decouple this from the process we would be saving considerable amounts of time.

For those not familiar with OSDBuilder it’s an awesome Powershell compilation created by David Segura that allows you to service your Windows Operating systems in an ‘offline’ fashion. Say you have a Server 2016 ISO that’s 4 years old. With OSDBuilder, you can ‘Import’ that ISO and apply all the necessary cumulative updates, in an automated offline fashion. So the next time you install Server 2016, it’s completely updated.

For me, this process looks something like this:

  1. ‘First Run’ Powershell script (only ran once)
  2. Patch Tuesday arrives
  3. Run ‘Install/Update OSDbuilder’ Powershell script (scheduled task)
  4. Run ‘Ongoing Updates’ Powershell script (scheduled task)
  5. Run MDT Task Sequence
  6. Rinse and Repeat (Steps 2-6)

As you can see you pretty much have a fully automated cycle that spits out a new #Citrix / #VMware Master Image with a fully updated OS.

Note: OSDbuilder follows WSUS release schedule, not Windows Updates.  Read more here  https://www.osdeploy.com/blog/microsoft-update-releases.

I’ll break this blog into 3 sections: Install/Update Module, First Run, Ongoing Updates. A big thank you to Julien for setting up the base code.

Install/Update Module

This section will install/update the necessary powershell modules. If the Module doesn’t exist at all, it will install it. Also, if the module finds an update, it will install the latest. It’s broken into two module sections, OSDBuilder and OSDSUS. OSDBuilder is the main module and OSDSUS is the module responsible for tracking the windows updates.

Latest release notes:

OSDBuilder – https://osdbuilder.osdeploy.com/release

#==========================================================================
#
# Automating Reference Image with OSDBuilder
#
# AUTHOR: Julian Mooren (https://citrixguyblog.com)
# DATE  : 03.18.2019
#
# PowerShell Template by Dennis Span (http://dennisspan.com)
#
#==========================================================================

# define Error handling
# note: do not change these values
$global:ErrorActionPreference = "Stop"
if($verbose){ $global:VerbosePreference = "Continue" }

# FUNCTION DS_WriteLog
#==========================================================================
Function DS_WriteLog {
    <#
        .SYNOPSIS
        Write text to this script's log file
        .DESCRIPTION
        Write text to this script's log file
        .PARAMETER InformationType
        This parameter contains the information type prefix. Possible prefixes and information types are:
            I = Information
            S = Success
            W = Warning
            E = Error
            - = No status
        .PARAMETER Text
        This parameter contains the text (the line) you want to write to the log file. If text in the parameter is omitted, an empty line is written.
        .PARAMETER LogFile
        This parameter contains the full path, the file name and file extension to the log file (e.g. C:\Logs\MyApps\MylogFile.log)
        .EXAMPLE
        DS_WriteLog -$InformationType "I" -Text "Copy files to C:\Temp" -LogFile "C:\Logs\MylogFile.log"
        Writes a line containing information to the log file
        .Example
        DS_WriteLog -$InformationType "E" -Text "An error occurred trying to copy files to C:\Temp (error: $($Error[0]))" -LogFile "C:\Logs\MylogFile.log"
        Writes a line containing error information to the log file
        .Example
        DS_WriteLog -$InformationType "-" -Text "" -LogFile "C:\Logs\MylogFile.log"
        Writes an empty line to the log file
    #>
    [CmdletBinding()]
    Param( 
        [Parameter(Mandatory=$true, Position = 0)][ValidateSet("I","S","W","E","-",IgnoreCase = $True)][String]$InformationType,
        [Parameter(Mandatory=$true, Position = 1)][AllowEmptyString()][String]$Text,
        [Parameter(Mandatory=$true, Position = 2)][AllowEmptyString()][String]$LogFile
    )
 
    begin {
    }
 
    process {
     $DateTime = (Get-Date -format dd-MM-yyyy) + " " + (Get-Date -format HH:mm:ss)
 
        if ( $Text -eq "" ) {
            Add-Content $LogFile -value ("") # Write an empty line
        } Else {
         Add-Content $LogFile -value ($DateTime + " " + $InformationType.ToUpper() + " - " + $Text)
        }
    }
 
    end {
    }
}
#==========================================================================

################
# Main section #
################

# Custom variables 
$BaseLogDir = "E:\Logs"                               
$PackageName = "OSDBuilder"

# Global variables
$date = Get-Date -Format yyy-MM-dd-HHmm
$StartDir = $PSScriptRoot # the directory path of the script currently being executed
$LogDir = (Join-Path $BaseLogDir $PackageName).Replace(" ","_")
$LogFileName = "$PackageName-$date.log"
$LogFile = Join-path $LogDir $LogFileName


# Create the log directory if it does not exist
if (!(Test-Path $LogDir)) { New-Item -Path $LogDir -ItemType directory | Out-Null }

# Create new log file (overwrite existing one)
New-Item $LogFile -ItemType "file" -force | Out-Null

# ---------------------------------------------------------------------------------------------------------------------------

DS_WriteLog "I" "START SCRIPT - $Installationtype $PackageName" $LogFile
DS_WriteLog "-" "" $LogFile

#################################################
# Update OSDBuilder PoweShell Module            #
#################################################

DS_WriteLog "I" "Looking for installed OSDBuilder Module..." $LogFile

try {
      $Version =  Get-ChildItem -Path "C:\Program Files\WindowsPowerShell\Modules\OSDBuilder" | Sort-Object LastAccessTime -Descending | Select-Object -First 1
      DS_WriteLog "S" "OSDBuilder Module is installed - Version: $Version" $LogFile
     } catch {
              DS_WriteLog "E" "An error occurred while looking for the OSDBuilder PowerShell Module (error: $($error[0]))" $LogFile
              Exit 1
             }

DS_WriteLog "I" "Checking for newer OSDBuilder Module in the PowerShell Gallery..." $LogFile

try {
      $NewBuild = Find-Module -Name OSDBuilder
      DS_WriteLog "S" "The newest OSDBuilder Module is Version: $($NewBuild.Version)" $LogFile
     } catch {
              DS_WriteLog "E" "An error occurred while looking for the OSDBuilder PowerShell Module (error: $($error[0]))" $LogFile
              Exit 1
             }

 if($Version.Name -lt  $NewBuild.Version)
  {
  try {
         DS_WriteLog "I" "Update is available. Update in progress...." $LogFile
         OSDBuilder -Update
         DS_WriteLog "S" "OSDBuilder Update completed succesfully to Version: $($NewBuild.Version)" $LogFile
       
     } catch {
              DS_WriteLog "E" "An error occurred while updating the OSDBuilder Module (error: $($error[0]))" $LogFile
              Exit 1
             }
  }

else {DS_WriteLog "I" "Newest OSDBuilder is already installed." $LogFile}

DS_WriteLog "I" "Trying to Import the OSDBuilder Module..." $LogFile

try {
        Import-Module -Name OSDBuilder -Force
        DS_WriteLog "S" "Module got imported" $LogFile
     }  catch {
              DS_WriteLog "E" "An error occurred while importing the OSDBuilder Module (error: $($error[0]))" $LogFile
              Exit 1
             }

DS_WriteLog "-" "" $LogFile

#################################################
# Update OSDSUS PoweShell Module            #
#################################################

DS_WriteLog "I" "Looking for installed OSDSUS Module..." $LogFile


try {
      $Version =  Get-ChildItem -Path "C:\Program Files\WindowsPowerShell\Modules\OSDSUS" | Sort-Object LastAccessTime -Descending | Select-Object -First 1
      DS_WriteLog "S" "OSDSUS Module is installed - Version: $Version" $LogFile
     } catch {
              DS_WriteLog "E" "An error occurred while looking for the OSDSUS PowerShell Module (error: $($error[0]))" $LogFile
              Exit 1
             }

DS_WriteLog "I" "Checking for newer OSDSUS Module in the PowerShell Gallery..." $LogFile


try {
      $NewBuild = Find-Module -Name OSDSUS
      DS_WriteLog "S" "The newest OSDSUS Module is Version: $($NewBuild.Version)" $LogFile
     } catch {
              DS_WriteLog "E" "An error occurred while looking for the OSDSUS PowerShell Module (error: $($error[0]))" $LogFile
              Exit 1
             }



 if($Version.Name -lt  $NewBuild.Version)
  {
  try {
         DS_WriteLog "I" "Update is available. Update in progress...." $LogFile
         Update-OSDSUS
         DS_WriteLog "S" "OSDSUS Update completed succesfully to Version: $($NewBuild.Version)" $LogFile
       
     } catch {
              DS_WriteLog "E" "An error occurred while updating the OSDSUS Module (error: $($error[0]))" $LogFile
              Exit 1
             }
  }


else {DS_WriteLog "I" "Newest OSDSUS is already installed." $LogFile}


DS_WriteLog "I" "Trying to Import the OSDSUS Module..." $LogFile


try {
        Import-Module -Name OSDSUS -Force
        DS_WriteLog "S" "Module got imported" $LogFile
     }  catch {
              DS_WriteLog "E" "An error occurred while importing the OSDSUS Module (error: $($error[0]))" $LogFile
              Exit 1
             }


DS_WriteLog "-" "" $LogFile


# ---------------------------------------------------------------------------------------------------------------------------


DS_WriteLog "-" "" $LogFile
DS_WriteLog "I" "End of script" $LogFile

Get-Content $LogFile -Verbose

First Run

This section will talk about how to first get started by importing your OS Media into OSDBuilder. This section really only needs to be run once.

OSDBuilder

Here is what happens:

  • Install/Update OSDbuilder
  • Import Server 2016 ISO into OSDBuilder
    • lives in the ‘OSImport’ directory
  • Update OS with latest updates
    • lives in the ‘OSMedia’ directory

The next time you run OSDBuilder, you don’t have to import the Media. You can simply run the commands in the ‘On-going Updates‘ section below. OSDBuilder will look in inside the ‘OSMedia’ folder, look for any updates from the last time it was run and apply the delta.

This code focuses on Server 2016. However, same code would apply for different Operating Systems.

# https://citrixguyblog.com/2019/03/19/osdbuilder-reference-image-on-steroids/

Import-Module -Name OSDBuilder

# Needs to be set for the rest of this to work
Get-OSBuilder -SetPath "E:\OSDBuilder"

# Create mount point for Windows Server 2016 iso
& subst F: \\Fileshare-p1\Files$\Citrix\Microsoft\2016-ISO

# Select index 2 (Standard w/Desktop Experience), go right to the updates, skip the ogv menu
Import-OSMedia -Verbose -ImageIndex 2 -SkipGrid

# Now we update the media
# the -SkipComponentCleanup argument is critical for us - without it, the PVS Target Device driver will break if installed during a task sequence 
Get-OSMedia | Sort ModifiedTime -Descending | Select -First 1 | Update-OSMedia -Download -Execute -SkipComponentCleanup

# With the updates complete, we create a build task
New-OSBuildTask -TaskName 'Task' -CustomName 'Test' -EnableNetFx3 -EnableFeature

# Cleanup mount point for 2016 iso
& subst F: /D

Note: You’ll want to change your paths to match your needs. The “Get-OSBuilder -SetPath “E:\OSDBuilder” is where you want OSDBuilder to create it’s directory/file/folder structure. In my case I put it on another drive. You’ll also want o change the path for your ISO location. And also take note of the -SkipComponentCleanup. You’ll need this if you are using Citrix Provisioning Services.

On-going Updates

The ‘On-going Updates’ section Gets/Applies Windows Updates to the lasted updated ‘OSMedia’ directory. Meaning that you’ll only be applying delta updates to the previous one you ran. If you want to apply updates from scratch, that’s configurable as well.

#==========================================================================
#
# Automating Reference Image with OSDBuilder
#
# AUTHOR: Julian Mooren (https://citrixguyblog.com)
# DATE  : 03.18.2019
#
# PowerShell Template by Dennis Span (http://dennisspan.com)
#
#==========================================================================


# define Error handling
# note: do not change these values
$global:ErrorActionPreference = "Stop"
if($verbose){ $global:VerbosePreference = "Continue" }


# FUNCTION DS_WriteLog
#==========================================================================
Function DS_WriteLog {
    <#
        .SYNOPSIS
        Write text to this script's log file
        .DESCRIPTION
        Write text to this script's log file
        .PARAMETER InformationType
        This parameter contains the information type prefix. Possible prefixes and information types are:
            I = Information
            S = Success
            W = Warning
            E = Error
            - = No status
        .PARAMETER Text
        This parameter contains the text (the line) you want to write to the log file. If text in the parameter is omitted, an empty line is written.
        .PARAMETER LogFile
        This parameter contains the full path, the file name and file extension to the log file (e.g. C:\Logs\MyApps\MylogFile.log)
        .EXAMPLE
        DS_WriteLog -$InformationType "I" -Text "Copy files to C:\Temp" -LogFile "C:\Logs\MylogFile.log"
        Writes a line containing information to the log file
        .Example
        DS_WriteLog -$InformationType "E" -Text "An error occurred trying to copy files to C:\Temp (error: $($Error[0]))" -LogFile "C:\Logs\MylogFile.log"
        Writes a line containing error information to the log file
        .Example
        DS_WriteLog -$InformationType "-" -Text "" -LogFile "C:\Logs\MylogFile.log"
        Writes an empty line to the log file
    #>
    [CmdletBinding()]
    Param( 
        [Parameter(Mandatory=$true, Position = 0)][ValidateSet("I","S","W","E","-",IgnoreCase = $True)][String]$InformationType,
        [Parameter(Mandatory=$true, Position = 1)][AllowEmptyString()][String]$Text,
        [Parameter(Mandatory=$true, Position = 2)][AllowEmptyString()][String]$LogFile
    )
 
    begin {
    }
 
    process {
     $DateTime = (Get-Date -format dd-MM-yyyy) + " " + (Get-Date -format HH:mm:ss)
 
        if ( $Text -eq "" ) {
            Add-Content $LogFile -value ("") # Write an empty line
        } Else {
         Add-Content $LogFile -value ($DateTime + " " + $InformationType.ToUpper() + " - " + $Text)
        }
    }
 
    end {
    }
}
#==========================================================================

################
# Main section #
################

# Custom variables 
$BaseLogDir = "E:\Logs"                               
$PackageName = "OSDBuilder"

# OSDBuilder variables 
$OSDBuilderDir = "E:\OSDBuilder"    
$TaskName = "CitrixVDA2"  # Do not use the real name of the task file - Example: "OSBuild Build-031819.json" --> Build-031819

#MDT variables
$MDTShare = "E:\DeploymentShare"


# Global variables
$date = Get-Date -Format yyy-MM-dd-HHmm
$StartDir = $PSScriptRoot # the directory path of the script currently being executed
$LogDir = (Join-Path $BaseLogDir $PackageName).Replace(" ","_")
$LogFileName = "$PackageName-$date.log"
$LogFile = Join-path $LogDir $LogFileName


# Create the log directory if it does not exist
if (!(Test-Path $LogDir)) { New-Item -Path $LogDir -ItemType directory | Out-Null }

# Create new log file (overwrite existing one)
New-Item $LogFile -ItemType "file" -force | Out-Null


# ---------------------------------------------------------------------------------------------------------------------------


DS_WriteLog "I" "START SCRIPT - $Installationtype $PackageName" $LogFile
DS_WriteLog "-" "" $LogFile


#################################################
# Update of the OS-Media                        #
#################################################

DS_WriteLog "I" "Starting Update of OS-Media" $LogFile

try {
        $StartDTM = (Get-Date)
        Get-OSBuilder -SetPath $OSDBuilderDir
        DS_WriteLog "S" "Set OSDBuider Path to $OSDBuilderDir" $LogFile
        $OSMediaSource = Get-ChildItem -Path "$OSDBuilderDir\OSMedia" | Sort-Object LastAccessTime -Descending | Select-Object -First 1
        Update-OSMedia -Name $($OSMediaSource.Name) -Download -Execute -SkipComponentCleanup
	    $EndDTM = (Get-Date) 
        DS_WriteLog "S" "Update-OSMedia completed succesfully" $LogFile
		DS_WriteLog "I" "Elapsed Time: $(($EndDTM-$StartDTM).TotalMinutes) Minutes" $LogFile
     }  catch {
              DS_WriteLog "E" "An error occurred while updating the OS-Media (error: $($error[0]))" $LogFile
              Exit 1
             }


#################################################
# Creation of the New-OSBuild                   #
#################################################

DS_WriteLog "I" "Creating New-OSBuild" $LogFile

try {
        $StartDTM = (Get-Date)
        New-OSBuild -ByTaskName $TaskName -Execute -SkipComponentCleanup
        $EndDTM = (Get-Date)  
        DS_WriteLog "S" "OS-Media Creation for Task $TaskName completed succesfully" $LogFile
        DS_WriteLog "I" "Elapsed Time: $(($EndDTM-$StartDTM).TotalMinutes) Minutes" $LogFile
     }  catch {
              DS_WriteLog "E" "An error occurred while creating the OS-Media (error: $($error[0]))" $LogFile
              Exit 1
             }


#################################################
# Import the OS-Media to the MDT-Share          #
#################################################

DS_WriteLog "I" "Searching for OS-Build Source Directory" $LogFile

try {
        $OSBuildSource = Get-ChildItem -Path "$OSDBuilderDir\OSBuilds" | Sort-Object LastAccessTime -Descending | Select-Object -First 1
        DS_WriteLog "S" "Found the latest OS-Build directory - $($OSBuildSource.FullName) " $LogFile
     }  catch {
              DS_WriteLog "E" "An error occurred while searching the latest OS-Build directory (error: $($error[0]))" $LogFile
              Exit 1
             }


DS_WriteLog "I" "Importing Microsoft Deployment Toolkit PowerShell Module" $LogFile

try {
        Import-Module "C:\Program Files\Microsoft Deployment Toolkit\Bin\MicrosoftDeploymentToolkit.psd1"
        DS_WriteLog "S" "MDT PS Module got imported successfully" $LogFile
     }  catch {
              DS_WriteLog "E" "An error occurred while importing the MDT PowerShell Module (error: $($error[0]))" $LogFile
              Exit 1
             }


DS_WriteLog "I" "Adding MDT Drive" $LogFile


try {
        New-PSDrive -Name "DS001" -PSProvider "MDTProvider" –Root $MDTShare -Description "MDT Deployment Share" 
        DS_WriteLog "S" "Created MDT Drive" $LogFile
     }  catch {
              DS_WriteLog "E" "An error occurred while creating the MDT Drive (error: $($error[0]))" $LogFile
              Exit 1
             }


DS_WriteLog "I" "Importing OS-Build to MDT" $LogFile

try {
        $date = Get-Date -Format yyy-MM-dd-HHmm
        New-Item -Path "DS001:\Operating Systems\2016\OSDBuilder-$date" -ItemType "Directory"
        Import-MDTOperatingSystem -Path "DS001:\Operating Systems\2016\OSDBuilder-$date" -SourcePath "$($OSBuildSource.FullName)\OS" -DestinationFolder "OSDBuilder-2016-$date"
        DS_WriteLog "S" "Imported latest OS-Build" $LogFile
     }  catch {
              DS_WriteLog "E" "An error occurred while importing the OS-Build (error: $($error[0]))" $LogFile
              Exit 1
             }



try {
        Remove-PSDrive -Name "DS001"
        DS_WriteLog "S" "Removed the MDT Drive" $LogFile
     }  catch {
              DS_WriteLog "E" "An error occurred while removing the MDT Drive (error: $($error[0]))" $LogFile
              Exit 1
             }




# ---------------------------------------------------------------------------------------------------------------------------


DS_WriteLog "-" "" $LogFile
DS_WriteLog "I" "End of script" $LogFile

You may have to edit these variables based upon your configuration.

Note that the ‘TaskName’ action is done in the ‘New-OSBuild’ section. This part can install any roles/features/languages that you may want to include that you don’t want to perform in your task sequence. If you just need Windows Updates, you can remove that whole ‘New-OSBuild’ section.

Also, be sure to modify the MDT import directory that matches how your structuring your Operating Systems.

Mine looks something like this:

If you need to check out the logs to diagnose or troubleshoot issues, everything gets logged to the ‘log directory’ you specify.

Summary

There you have it. You now have a way to Automate updating your Windows OS media by using OSDBuilder, which then gets imported into your MDT environment.

The next post I’ll go over the Task Sequence and how I’ve setup most of my installs.


  • 0

Building multiple Citrix Images with one Microsoft MDT task sequence

Continuing on the discussion of Automating the Citrix Image build process with MDT. One small problem is that there’s never just one image to do. You probably have an Image for a couple different line of businesses, another Image for these 3 Developers, an Administrator image, and maybe a few more images for other miscellaneous reasons.

At one point, the team I consulted for called their Citrix Desktop the ‘Unicorn Desktop’. We thought we could get away with having just 1 image. Very quickly we were reeled back to reality. While we keep striving to maintain just one image, we are always faced with some scenarios that keeps on adding more to the list. Technologies like App-V, App-V Scheduler, Powershell, ControlUP, and other automation vendors can help decrease the amount of images you maintain, but inevitably my guess is you’ll always have more than one.

If your using MDT/SCCM you’ll want to consolidate your efforts as much as possible. This means we’ll at least strive to have only one task sequence, the ‘Unicorn Task Sequence’ we’ll call it. So how does this look?

First, let’s start by examining the customsettings.ini.

[Settings]
Priority=Init,Image,Default
Properties=MyCustomProperty;BIOSVersion;SMBIOSBIOSVersion;ProductVersion,ComputerSerialNumber,MAC,OSVER

[Init]
ComputerSerialNumber=#Right("%SerialNumber%",7)#
MAC=#Replace("%MacAddress001%",":","")# 

[Image]
Subsection=%MAC%

[000000001234]
OSDComputername=CTX16LOB01
OSVER=16
TaskSequenceID=Prod

[000000001111]
OSDComputername=CTX16LOB2
OSVER=16
TaskSequenceID=test

[223344556688]
OSDComputername=CTX12DEV01
OSVER=12
TaskSequenceID=Steve-Shortened

[Default]
_SMSTSORGNAME=AAA
OSInstall=Y
DoNotCreateExtraPartition=YES
UserDataLocation=AUTO
TimeZoneName=Central Standard Time

SLShare=\\monitor.domain.com\Logs$
ScanStateArgs=/ue:*\* /ui:AAA\*
HideShell=YES
ApplyGPOPack=NO

SkipAdminPassword=YES
AdminPassword=supersecret
SkipApplications=YES
SkipBDDWelcome=YES
SkipBitLocker=YES
SkipComputerBackup=YES
SkipComputerName=YES
SkipDeploymentType=YES
SkipDomainMembership=YES
SkipUserData=YES
SkipFinalSummary=YES
SkipLocaleSelection=YES
SkipPackageDisplay=YES
SkipProductKey=YES
SkipRoles=YES
SkipSummary=YES
SkipTaskSequence=YES
SkipTimeZone=YES

DeploymentType=NEWCOMPUTER
SkipCapture=YES

;FinishAction=REBOOT

EventService=https://monitor.domain.com:9800

The [Settings] section tells how MDT is going to traverse the different sections below.

Settings

So when MDT process starts, it will start with the ‘Init’ section, then move onto ‘Image’, then ‘Default’. The ‘Properties=’ defines variables that are used in the different sections.

In our case, the important variable here is the MAC variable. In this case, it’s the MAC address.

When the machines boots, it will define the MAC variable with it’s MAC address, without ‘:’. Example: MAC=001122334455

After the ‘Init’ section, MDT moves onto ‘Image’

From here, MDT says goto the subsection, which is the MAC address variable. The subsections are defined with the brackets, ex: [subsection] , and can contain anything. In our case, the subsections contain the MAC address with a couple other variables defined. If the machine you are using has a MAC address of ‘000000001234’, MDT will process that following subsection.

Here is what the actual VMs look like on your hypervisor of choice.

Now that we are in the subsection [000000001234], we are defining a couple variables. OSDComputername, OSVer, and TaskSequenceID. The OSDComputername is what we will name the computer in Windows, this should match the VM name as well. The OSVER variable defines our Operating System version. This get’s called later in the actual task sequence. The TaskSequenceID is the ID of the actual task sequence that is used. You can get this from MDT console. You can see in the examples we are calling different task sequences depending on what machine(MAC) we are using. Now I know the title of this blog says ‘One’ task sequence. This is true for our actual Production runs. However, we do have other task sequences defined. For example, when we want to run a shortened version, or if we want to simply skip the OS run and just want to test out one or two application installs.

After the specific MAC subsection gets called. MDT moves onto the [Default] section. This section defines lots of different settings on how you want MDT to operate, alert, and prompt. I won’t go into each one in detail here as there are lots of websites that go into detail. However, those are the settings that I use for our production MDT environment.

By the time the [Default] section is done, the specific Task Sequence you used in ‘TaskSequenceID’ should be starting. Once it gets to the ‘Install’ part, we create separate ‘Groups’/Folders for our Operating Systems. We then can add a ‘condition’ if the OSVER variable equals whatever value we chose in the customsettings.ini section. (We could also key off the machine name here too)

On the ‘Server 2016′ group, if the OSVER is equal to ’16’, then it installs Server 2016. On the ‘Server 2012R2′ group, we change the OSVER value to ’12’. After the OS is installed, the task sequence just moves down the list. The ‘State Restore’ is the TS section that contains most of your image content, such as application installs, registry settings, custom powershell scripts, etc…

Notice we have a ‘Commands’ section. This contains different settings such as creation of log directories, setting time, disabling UAC, IPv6, System Restore, Page file and other related settings. ‘Patches’ and ‘PreReqs’ are groups that you may need to use to apply specific patches that your OS/WIM may not already have. Maybe an out of band Windows Update or something similar. The ‘Global Intalls’ group contains applications that are installed no matter what Image we are deploying. This is where I put C++ redistributes, Office, Silverlight, Chrome, Firefox, Adobe Reader, BIS-F, etc… The ‘Roles and Features’ contains unique Windows Roles and Features we want installed. ‘Citrix Installs’ contains all the necessary installs for Citrix, like, Receiver, VDA, Connection Quality Indicator, PVS Target Devices, etc…

The 3 ‘blurred’ groups are the specific different Images you are creating, Maybe the first one says ‘Admin Image, ‘Line of Business 1’ or ‘Developers’ or something like that. In my case we’ll call it ‘Admin Image’. This image will contain necessary tools to Administer the environment such as ADUC, Citrix Studio, and all the rest of the necessary consoles to Admin the environment.

As you can see above I’m creating another ‘condition’. If the computername (OSDComputername variable) is like ‘CTX%ADM%’. So if the VM/Machinename we were using was ‘CTXVDA-ADM001’ then MDT would execute this portion of the task sequence.

The ‘Final Installs’ Group contains any final activities. This is actually where I put my AV install, miscellaneous security fixes, compliance requirements, etc…

The ‘Seal’ Group contains the the final domain join, MDT log and notification/alerts, and BIS-F seal.

I hope you now are able to see how you can have a bunch of ‘shell’ VMs that are named differently according to what Image/Task Sequence you want to execute. With this approach we can start simultaneous machine builds at once, without stepping on any toes. You can size the VMs to whatever CPU/Memory size you like. I size them pretty big as I want this process to go quick and since they aren’t always on at once, I’m not too concerned about resources.

If you are just getting started I would encourage you to head over to https://xenappblog.com/ and check out his ‘Automation Framework’. Eric does an awesome job on simplifying the install and giving you the foundation you need to take this whole process from nothing to a production task sequence with little effort.

In future blog posts I’d like to go into a little more detail on the Task Sequence itself, how I create the OS WIM (OSDbuilder), Different types of alerting/monitoring I’m performing, and some other tips and tricks that you might find useful.


  • 0

Citrix + Microsoft MDT + Powershell + Tools: The Beginning.

Building Golden Images, Master Images, References Images (whatever you want to call them) is a necessity in today’s world. All too often I see Citrix customers using persistent VMs built manually. Sometimes 20-50 of the same VMs, going through the same manual clicks for upgrades, the same manual motions for software pushes, windows updates, etc… NO MORE PLEASE!!

Citrix PVS and MCS have helped pave the way for non-persistent VMs. <insert tangent> I’d really like to see this approach take over in the infrastructure role world too. Think stateless VMs for Citrix ADC, Delivery Controllers, Storefront, and other infrastructure roles <end tangent>. As soon as a persistent machine is built, your guess is as good as mine on what’s all changed on it from day to day. Maybe someone ran a script that changed something, maybe a bad windows update came in, SCCM pushed a bunch of software to it accidentally, the list goes on.

MDT isn’t the only product to build golden images. You can certainly use SCCM, Puppet, Terraform, Ansible, Chocolatey, Jenkins, etc… the list goes on and on. IMO MDT is one of the easiest and windows friendly options, but it’s nice to have other tools in the arsenal to solve whatever use case comes up. Like using Hashicorp Packer to create the VM and auto boot it into the MDT task sequence.

Over the next few months I’ll be sharing some blog posts regarding tips, tricks, gotchas, “how I did it” type examples of ways I’m leveraging MDT.


  • 2

System Center Endpoint Protection & Config Manger in Citrix Xenapp/PVS

antivirus-heading

One ocitrix-logo-250x250f my clients is undergoing a complete Datacenter transformation.  With this comes a lengthy list of multiple decisions on what to bring over from the old to the new.  This post will focus on transitioning their current virtual machine anti-virus protection to a new solution.  The new solution is System Center Configuration Manager/Endpoint Protection in a Citrix XenApp/PVS environment.  This consists of Citrix Xenapp 6.5, PVS 7.1, and System Center 2012 R2.  Although, I don’t really see any reason these same steps wouldn’t work in  later versions of Xenapp and PVS.

Currently they are using Trend Micro Deep Security 9.5 and OfficeScan 11.x to protect their Endpoints as well as their virtual machines.  I have to say, I do really like Deep Security to protect the virtual machines.  Very little maintenance is required and it just seems to do its job well.  Unfortunately, somebody has to pay the bills to keep it licensed.  The client decided to utilize their current Microsoft ‘Enterprise Agreement’ licensing and protect themselves with System Center Endpoint Protection, with Config Manager managing the agents.

While I do realize there are other blog posts out there that contain similar information on this topic, I was not able to use just one site to complete this project.  At the bottom of the post I document where I got most of the resources used to complete this project.SystemCenterEndpointProtection

First, we need to install SCCM into the image.  SCCM is used to manage and control the agents centrally.  It is used to control all the special exclusions, real-time scan settings, etc…  Without it, you would have to manage each agent manually.

SCCM Steps:

  1. Create a new PVS Version
  2. Install the ConfigMgr client
    a. Launch the SCCM setup client from the SCCM server – \\SCCMserver\Client\CCMSetup.exe
    b. Add ‘domain\username’ domain account as local admin on the xenapp 6.5 server.

SCEP Steps:

  1. Run bat file:
    1. MKDIR e:\SCEP
      1. this is the ‘cache drive’ used to store RAM overflow, event viewer, pagefile, and SCEP anti-virus definitions.
    2. cmd.exe /c Mklink /d /j “C:\ProgramData\Microsoft\Microsoft Antimalware” E:\SCEP
    3. Install SCEP executable
      \\servername\Client\SCEPinstall.exe
  2. In SCCM After SCEP/SCCM is deployed:
    1. Create Device Collection
      1. Add your servers to the collection that you are installing SCEP on.
    2. Create Endpoint Protection Policy
      1. Apply to Device Collection (Device/Antimalware policies tab)
        1. SCEP-antimalware-policy
    3. Create Deployment Endpoint Protection Policy
      1. Apply to Device Collection (Device/Client settings tab)
        1. SCEP-protection-policy

Final Steps:

  1. Seal Image using the following bat file:
    1. Net stop “SMS Agent Host”
    2. del %WINDIR%\smscfg.ini
    3. Powershell -command “Remove-Item -Path HKLM:\Software\Microsoft\SystemCertificates\SMS\Certificates\* -Force”
    4. wmic /namespace:\\root\ccm\invagt path inventoryActionStatus where InventoryActionID=”{00000000-0000-0000-0000-000000000001}” DELETE /NOINTERACTIVE
    5. Del “c:\programdata\citrix\pvsagent\LocallyPersistedData\CCMData\CCMCFG.bak”
  2. Use Xenapp Role Manager to do the final seal and shutdown the image.

Confirm Steps:

  1. Confirm SCCM Policy Deployment to the SCEP agent (on the SCEP agent, drop down arrow, about)
    1. SCCM-confirm3
  2. Confirm the devices are manageable in SCCM
    1. SCCM-confirm1
    2. SCCM-confirm2
  3. Test Protection
    1. I was able to test protection using a ‘dummy’ anti-virus test file
    2. http://www.eicar.org/
      1. create the .txt file with the string in it.
      2. Safe the file
    3. SCEP should recognize it as a threat and clean the file appropriately

That’s it, your Citrix PVS machines should now be protected by System Center Configuration Manager and System Center Endpoint Protection.  Now I realize there are 100 ways to skin a cat.  Please let me know if have/find any efficiency’s in the steps and i’ll be happy to change the contents of the site.

A big THANK YOU to everyone involved in creating and helping answer questions on the following sites:

SCCM:

SCEP:


Twitter