So far we’ve been able to show you how to configure your MDT task sequences, customsettings.ini, and automatically update your Operating System media via OSDBuilder.

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

Part 2: Building multiple Citrix Images with one Microsoft MDT task sequence

Part 3: Automate updating OS ISOs with OSDBuilder and MDT

Now that we have an updated OS (The Meat) to use, it’s time to talk about the applications (The Potatoes). Applications are going to be different across environments, but using a template to install them can create standards and efficiencies. For this to be effective, 99% of my application installs are done via Powershell scripts. When adding a new application, I’ll always choose “Application without source files or elsewhere on the network”.

MDT New Application

There’s a few reason I do it this way.

  • Easier with Application upgrades. It’s much easier to change the powershell script than create/edit the application entry in MDT Workbench
  • Can create custom logging. You can build in whatever logging you like.
  • Better conditional logic. Maybe you want to intsall the Application on Server 2012, but not Server 2016. It’s easier to do this with 1 powershell script, than multiple Workbench Applications.

As mentioned above I use a custom created template that has most of what I need already baked in. Here is what it looks like.

#USERNAME, Date of Change, What Changed
#snoel, 1/5/2020, updated start-process command

$Source = "\\FileShare-FQDN\Share$\Vendor\App\"

#Update Source Folder Location
$SourceVersion = "NA"
$AppName = "NAME"
$AppVersion = "NA"
$Version = "V1"
$LogPath = "C:\Windows\MDT"

#Set Location
Set-Location $Source

Start-Transcript -Path "$LogPath\$AppName.$SourceVersion.$Version.PSLog.Txt"

try {

#install agents
Write-Host "Starting Installation of $AppName. $(Get-Date)"
    #insert process here you want to track time, such as install MSI, Copy Files, etc...
Write-Host "Finished Installation of $AppName. $(Get-Date)"

    Start-Process "$SourceVersion\googlechromestandaloneenterprise64.msi" -Wait -ArgumentList "/qn /norestart /l* $LogPath\$($AppName)MSI.$version.txt ALLUSERS=1"

#Powershell script
    Write-Host "Kicking off QA-Applications Powershell Script. $(Get-Date)"
    Start-Process "powershell.exe" -Wait -ArgumentList "-executionpolicy bypass \\FileShare\Share\Vendor\Application\example.ps1" -Credential $scriptCreds  
    Write-Host "Finished Kicking off QA-Applications Powershell Script. $(Get-Date)"

    Start-Process "setup-BIS-F-6.1.0_build01.104.exe" -Wait -ArgumentList "/SILENT /NORESTART /NOICONS /log=$logPath\$SourceVersion.$version.BIS-F.log"

    Start-Process "C:\Program Files (x86)\Base Image Script Framework (BIS-F)\PrepareBaseImage.cmd" -Wait

    REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\RC4 40/128" /f /v "Enabled"  /t REG_DWORD /d 00000000
    reg delete "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server" /v TSAdvertise /f

#File Copy
    Copy-Item -Path "$SourceVersion\x86\Deployment\Standard\*.*" -Destination "$JavaPath32\lib\" -Force -Verbose

Write-Host "Finished Installation of $AppName. $(Get-Date)"

Write-Host "Starting Copy of Powershell File. $(Get-Date)"
copy-item $($MyInvocation.MyCommand.path) $LogPath\scripts -Verbose
Write-Host "Finished Copy of Powershell File $(Get-Date)"
catch {
"Start CATCH $(Get-Date)" | Tee-Object -FilePath $LogPath\Errors.txt -Append
$($MyInvocation.MyCommand.path) | Tee-Object -FilePath $LogPath\Errors.txt -Append
$_ | Tee-Object -FilePath $LogPath\Errors.txt -Append
"END CATCH" | Tee-Object -FilePath $LogPath\Errors.txt -Append


To break this down a little bit I’ll go over the sections in detail.

Section: Comments

This basically just give s a brief description of what the script does. The next lines gives us a little version control so you can see what was done, by who and when. Ultimately if you can use git or another version control system, that would be best.

Section: Variables

This section we set all the variables. Starting off with $Source, which is where we will set the working directory in the script. The $Sourceversion will be the actual version we are installing. We try to setup the folder structure so it runs like so:

That way when we need to install another version, we simply change the $SourceVersion variable and everything else can stay the same.

The $Version variable is there if you want to actually version your powershell script, this is moot should you use a verison control platform.

The $LogPath is the directory we set for where the powershell ‘transcript’ will go.

The $AppName and $AppVersion variables are there for me to do QA at a later date. Basically the gist goes like this:

  • Each powershell script gets copied to a local directory on the image
  • I go through each file….if the $Appname and $AppVersion are set, then perform a check
  • The check is to look at the registry locations for all installed programs (x32 and x64)
  • The $Appname and $AppVersion from the Powershell File must match whats in the registry. If it does, it passes QA
  • If QA fails on an app, then take some sort of action.

I’ll go through this process in a future blog.

Section: Install

Here we will start off with a ‘Start-Transcript’ which will log things that happen. Then on line 18 we start the “Try/Catch” blocks. Everything install wise pretty much happens within this Try/Catch block. When doing the actual install, I like to do a Get-Date before and after, which is denoted by lines 21 and 23. Line 22 is the actual command to install the application. Sometimes it’s just a one liner for apps like Chrome, Firefox, etc… Other times it may require multiple lines for the silent install.

Here are some examples of things you might see on line 22.

Section: Copy Script

This section copies the powershell script to the C:\windows\mdt\scripts directory. I like to do this for a couple reasons.

  • If you need to look historically (or currently) at your images to see how something was installed, you have a reference.
  • I parse these files in a later process to perform ‘Quality Assurance’. I’ll get into this in a future blog.

Note: If there is a sensitive script that contains things i don’t want shared, then I simply don’t add these lines. However, this would be rare.

Section: Catch

Normally if there is a ‘non terminating’ error/warning in the script, that will get logged via the normal start/stop-transcript command. However, sometimes there is a ‘terminating’ error, in which case the script will actually stop completely. For these, I put them in an ‘errors.txt’. I haven’t 100% formed my plan yet, but I’m thinking this will be part of my QA process. In general, this file shouldn’t exist, b/c nothing should be terminating.

Hope this helps out some folks. Would love to hear some feedback on this so please leave a comment.

Leave a Reply

Your email address will not be published. Required fields are marked *