MDT Imaging in PDQ Deploy

The Microsoft Deployment Toolkit, or MDT, is an incredibly flexible, extensible, and powerful utility in the hands of any sysadmin. As times change and best practices mature, we’ve seen a shift from traditional “golden” images (with applications, settings, etc. all baked in) to thin images, easily updated and dynamically provisioned during the imaging process itself. However, outside of PDQ, with great power often comes great complexity. Using MDT Imaging in PDQ Deploy can streamline the process of installing applications during the imaging process. You can simply queue a PDQ  deployment from inside MDT! It’s the perfect match, just like peanut butter and chocolate.

Get Started Using MDT Imaging in PDQ Deploy

There are a few schools of thought on how you should deploy applications to a newly-imaged computer. The simplest way, which requires no MDT changes, is from within PDQ Inventory and Deploy.

  1. Create new or use existing, dynamic collections that contain machines missing the applications you want to be installed. PDQ Inventory’s Collection Library has many dynamic collections for workstations missing applications. For example:

Collection Library Example 1

Collection Library Example 2

Collection Library Example 3

Collection Library Example 4

2. You can use a heartbeat trigger to create a schedule in PDQ Deploy linked to the appropriate collection and Auto-Download package.

MDT Imaging in PDQ Deploy - Newly Imaged Workstations - Target

MDT Imaging in PDQ Deploy - Newly Imaged Workstations - Package

MDT Imaging in PDQ Deploy - Newly Imaged Workstations - Heartbeat

3. That’s it. When PDQ Inventory sees those machines come online, it’ll queue the deployments as necessary.

This is a totally acceptable and very common way to deploy applications in an environment (especially things like Java and Adobe Reader updates), but there are also many cases where you want a freshly-imaged machine to be ready to go as soon as the task sequence completes.  With applications like the Microsoft Office suit, you’ll also want to ensure all the latest Windows Updates are applied.

Better Living Through PowerShell

You can use a PowerShell script in conjunction with MDT to trigger a PDQ deployment targeting the machine MDT is imaging for those cases where you want to have newly-imaged machines ready to distribute to your users.

As an example: When I set this up, I put a custom task between the Pre- and Post-Application Install Windows Update steps of my task sequence. This allows the first run of updates (cumulative updates, framework patches, etc.) to process before installing your desired suite of applications.  A second update run catches anything that is missed on the first go or that is newly required as a result of those installations.

Deploy Image - Custom Tasks

There are two main methods of kicking off the deployment inside MDT. One uses the utility PsExec, and the other uses PowerShell’s Invoke-Command cmdlet. Both options accomplish the same end result, however, depending on your environment, one may be preferred over the other.

Option 1 – PsExec

PsExec is an extremely powerful and handy utility for any sysadmin. From Microsoft’s documentation: “PsExec is a light-weight telnet-replacement that lets you execute processes on other systems, complete with full interactivity for console applications, without having to manually install client software.”

Things to note:
  1. PsExec is not included with Windows by default. You can use a command step in MDT to copy PsExec onto the machine being imaged from your MDT share like so:
    1. xcopy “%SCRIPTROOT%\PsExec.exe” “C:\WINDOWS” /Q /H /E /I /Y
  2. MDT runs commands in the context of whatever credentials you feed it (either in CustomSettings.ini or manually at the beginning of the task sequence). You can also change the setting for the PowerShell step to “Run this step as the following account.”
  3. Whatever account you use will need to be a Console User in PDQ Deploy. Your deployment will fail without this.

The script:

netsh advfirewall set allprofiles state off
ipconfig /registerdns

psexec.exe \\pdq.host.fqdn -h -accepteula ipconfig /flushdns
psexec.exe \\pdq.host.fqdn -h -accepteula pdqdeploy.exe Deploy -Package 
"New PC Setup" -Targets $env:COMPUTERNAME

start-sleep 30
while(test-path "C:\Windows\AdminArsenal\PDQDeployRunner\service-1.lock"){
start-sleep 30
}

How this works:

  1. Temporarily disables the firewall on the machine. Reset the group policy settings after a reboot.
  2. Re-registers the machine in DNS. PDQ software is highly dependent on a healthy, up-to-date DNS.
  3. Flushes the DNS cache on the PDQ server to ensure it reflects the correct IP > Hostname mapping for the machine being imaged.
  4. Initiates a CLI deployment of “New PC Setup” targeting the hostname of the machine being imaged. Replace the package name with whatever package you want to deploy. Nested packages are great here.
  5. Wait. We want PDQ Deploy to have time to actually start the deployment, especially if you do a lot of deployments and it might end up in the queue.
  6. Wait more. Now we’re watching service-n.lock, which is a file generated by the PDQ Deploy runner service. As long as that file exists, the PDQ deployment is ongoing and MDT will not progress to the next step in your task sequence. PDQ Deploy is done once it’s gone. We can move onto the next step in the MDT task sequence!

Option 2 – Invoke-Command

Invoke-Command is a stock cmdlet starting with PowerShell 3.0 and onward. From Microsoft: “The Invoke-Command cmdlet runs commands on a local or remote computer and returns all output from the commands, including errors.”

Things to note:
  1. PDQ Deploy must be running in Central Server mode.
  2. Create a registry key on your PDQ Deploy server to make the background service use TCP instead of Named Pipes:
    1. Location: HKEY_LOCAL_MACHINE\SOFTWARE\Admin Arsenal\PDQ Deploy
    2. Value: ServicePort (DWORD value)
    3. Set the ServicePort value to a port number you’d like to use (outside of the well-known port range, and not 6336 or 7337), then restart the background service.
      1. Depending on your environment: Configure the firewall on your PDQ Deploy server to allow connections on that port.
  3. As with PsExec, this will run in the context of your MDT user, ensure that the user is set up as a Console User in PDQ Deploy.
  4. Because the cmdlet returns all command output, including errors, you’ll need to add some redirection so that MDT doesn’t think something is wrong.

 

The script:

netsh advfirewall set allprofiles state off
ipconfig /registerdns

Invoke-Command -ComputerName pdq.host.fqdn -ScriptBlock  {ipconfig
/flushdns; pdqdeploy.exe Deploy -Package "New PC Setup" -Targets
$args[0]} -Args $env:COMPUTERNAME 2>&1

start-sleep 30
while(test-path "C:\Windows\AdminArsenal\PDQDeployRunner\service-1.lock"){
start-sleep 30
}

This one works in a similar fashion to the PsExec version. By passing $env:COMPUTERNAME as an argument, we refer to it inline as $args[0], as it’s the first object in that array. We redirect the output using 2>&1 because MDT assumes that no news is good news, and also assumes the inverse.

Wrapping It Up

MDT is an extremely versatile tool that can be made even better with the power of PDQ Deploy. By using this functionality, you can increase the efficiency of your imaging process, leaving you with more time for the fun things in life, like fixing your mangled DNS servers and drinking! If you’re looking for more information on the subject, you can also refer to the PDQ Live! webcast on this subject.

Good luck and happy imaging!

-Jake

 

12 responses

  • I’ve added my powershell-only solution which converts computername to IP address, puts in a timeout incase the deployment never starts, so MDT doesn’t wait indefinitely.

    The powershell command would be:

    powershell.exe -executionpolicy bypass \\PATH-TO-Deploymentshare\PDQ\PDQ-Deploy-MDT-WithWait.ps1 -package ‘Insert Package Name Here’

    I set the task step to run as a specific account that has PDQ access.

    Code For PDQ-Deploy-MDT-WithWait.ps1:

    #Declare the parameter for package name
    param (
    [Parameter(Mandatory=$true)][string]$package
    )

    # Find the ip address from the computername – helps to use IP if you have unreliable DNS
    $ipV4 = Test-Connection -Computername “$env:COMPUTERNAME” -count 1 |Select -ExpandProperty
    IPV4Address

    # Run the deployment command using ip address as the target
    Invoke-Command -ComputerName PDQ01 -ScriptBlock { param ($compname) & ‘C:\Program Files (x86)\Admin
    Arsenal\PDQ Deploy\pdqdeploy.exe’ Deploy -Package $Using:package -Targets $Using:ipV4.IPAddressToString} -ArgumentList “$env:COMPUTERNAME”

    # Run the deployment command using computername address as the target
    #Invoke-Command -ComputerName PDQ01 -ScriptBlock { param ($compname) & ‘C:\Program Files (x86)\Admin Arsenal\PDQ Deploy\pdqdeploy.exe’ Deploy -Package $Using:package -Targets $compname} #-ArgumentList “$env:COMPUTERNAME”

    #Add a timeout so if the deployment doesn’t start it continues after 60 minutes
    $timeout= new-timespan -Minutes 60
    $StopWatch = [diagnostics.stopwatch]::StartNew()

    #wait for the package to start by waiting for the lock file to appear
    ## This is good for when deployments may be queued up if PDQ deployment server is heavily used.
    $LockfileExist=$false
    Do{
    If(Test-Path ‘c:\windows\AdminArsenal\PDQDeployRunner\service-1.lock’) {$LockfileExist = $true} Else {Write-
    Host ‘Waiting PDQ install to start on ‘ $env:COMPUTERNAME – $ipV4.IPAddressToString ; Start-Sleep -s 10}
    }
    Until (($LockfileExist) -or ($StopWatch.elapsed -ge $timeout))

    ### Check if the package is still running by looking for the lock file to disappear
    $fileDeleted=$false
    Do{
    If(Test-Path ‘c:\windows\AdminArsenal\PDQDeployRunner\service-1.lock’) {Write-Host ‘PDQ install started:
    waiting to complete on ‘ $env:COMPUTERNAME – $ipV4.IPAddressToString; Start-Sleep -s 10} Else {$fileDeleted
    = $true}
    }
    Until ($fileDeleted)

  • sorry, but this does not work.
    you are missing one verry big problem: mdt is running everything under local administrator account.

    • Hey there,

      This does actually work, and I have tested it on multiple occasions to verify that. I’m not certain which, but I know the script runs as one of two users: the account defined in bootstrap.ini, or the account defined for the DomainAdmin variable in customsettings.ini.

      -Jake

    • At the top of my entry i mention “I set the task step to run as a specific account that has PDQ access.”
      It is on the task sequence properties tab.
      There is a checkbox, Run this step as the following account
      Where you can set the account . I use a domain account that has Admin access to PDQ.
      I dont change any MDT ini files.

  • After seeing how you are able to remotely run pdqdeploy with PsExec it got me thinking. Can I run any package from PDQ Deploy this way? So I decided to try created a script to self-deploy any package to itself and it works! Checkout the ‘self_pdqdeploy’ script from my public repository,
    http://gitlab.deserttech.com/chris/ccollins-pdq/

    The practical solution for this would be where you need to deploy a package and you’re not on the PDQ Deployment server. Of course you can just remote desktop in but what if someone else is using it? So it’s just nice if you want to give access to other computers on your network to “self-deploy” packages to themselves. Also with security in mind they would need Admin rights to access the network-share containing the actual script.

    It’s my first working version so let me know if I could have done anything better. I’m tempted to add Erin’s solution for running deployments with the IP address as the target.

    • Hey Chris!

      We do have clients ask from time to time about a self-service portal, and while it is something you can do, it’s not something we currently provide. I do really like the solution you’ve come up with, though! One thing to remember here is that if you are using it in that fashion, each of those connections is seen as a licensed admin connection. As we license on a per-admin basis, each of those would need to be licensed. So, if you allowed every user in your company to self-deploy, you would basically need a Deploy license for each user in your company.

      We’ve created our Enterprise SL license to allow unlimited concurrent connections, which fulfills that licensing requirement. Otherwise, you’ll need to restrict the use of this to your licensed admins.

      Other than that note, excellent work! The only change that pops out at me immediately is removing the netsh advfirewall set allprofiles state off line, as your firewall should be configured at this point via GPO/management console (if using a third-party solution), so you shouldn’t need to disable it if configured properly. 🙂

      Also, if you want to be able to kick off deployments without having to RDP into your Deploy machine and have an Enterprise license, may I recommend Central Server mode? You can simply have your workstation configured as a client console so you and your fellow admins don’t have to RDP in anymore.

      Cheers,
      Jake

      • Oh good catch on the netsh there’s no reason to disable the firewall. Ya that makes perfect sense on the licensing I forgot about Central Server Mode.

  • Question.
    I had some difficulty getting invoke to work because of the credentials, but was able to use command line and “run as” to run the powershell command and use a domain account with admin access to PDQ. The only other problem left is that I can see in the powershell window during deployment that it is complaining because the invoke is not running as administrator so flushdns and turning the firewall off is not working. Most of my deployments are successful anyways without this, but I think it would help to make a higher percentage of them successful. Any suggestions to make it runas admin?

    • This makes it work.
      If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))

      {
      $arguments = “& ‘” + $myinvocation.mycommand.definition + “‘”
      Start-Process powershell -Verb runAs -ArgumentList $arguments
      Break
      }

  • I tried this in an MDT task sequence and, though it seems like there are a few things missing from this post describing how to set it up, that’s actually not what my post is about.

    Since it wasn’t working for me during a task sequence, I just decided to run powershell as a user with console access on the machine after imaging, to try to catch what the issue is, and I run into the error:
    An unexpected error happened while communicating with the server…
    Then I go to the server’s event log and I see:
    The pipe name could not be obtained for the pipe URI: Access is denied. (5, 0x5)

Your email address will not be published.

Your Name