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 \\ -h -accepteula ipconfig /flushdns
psexec.exe \\ -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 -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!


Start a trial of PDQ Deploy

16 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 (

    # 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

    # 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.
    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
    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.


    • 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,

    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.


      • 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

  • 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)

  • I know this post is a few months old, but I want to add to this as I originally followed this thinking it would be great to have this all done automatically instead of deploying the apps manually after the MDT image is finished. Unfortunately it did not work as I had hoped and as a result I have spent a crazy amount of time getting this to work properly with MDT in my environment.

    First I want to say that we use MDT to deploy just a basic Windows 7 image with nothing installed except for Windows Updates. We have 6 IT people working at my organization and I was trying to make everything work as smoothly as possible without having anyone remember what needs added to the devices.

    Second I used Option 1 – PsExec as detailed above by Jake. However, I called the Powershell script in MDT by using a ‘Run Command Line’ Task Sequence in MDT and telling it to run a batch file that in turn ran the Powershell script Jake lays out above. This was done so 1)I could run the Powershell script as a user that is able to authenticate to the PDQ Deploy Server and 2) I could change the script at anytime without having to change anything in MDT. The Batch file script simply said:


    powershell.exe -exectutionpolicy bypass -file \\MDT Server\deploymentshare$\scripts\PDQ-Deploy-W7Pro.ps1 -verb runas


    However when imaging, I was getting the error message that everything needed to be ran as an administrator. I then saw David’s post about using this script to elevate to an administrator. Just place it at the beginning of your PowerShell script and make sure the user running the script is an administrator for the PC.


    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


    This also did not work as now it was telling my that

    ‘File \\MDT Server\deploymentshare$\scripts\PDQ-Deploy-W7Pro.ps1 cannot be loaded because the execution of scripts is disabled on this system. Please see “get-help about_signing” for more details.’

    After lots of research and trial and error I found a way to set the execution policy for Powershell to Unrestricted for any PC in a particular OU. As such I created a new OU in Active Directory and set the customsettings.ini (The one under the deploymentshare properties and the Rules tab) file in MDT to join the PC to the domain and place the PC in the new OU so the script above will run without issue. As soon as the PC is imaged and deployed I just have to move the PC to the correct OU and the execution policy returns to the default setting.

    For reference the location to set the execution policy in Active Directory is:
    Computer Configuration –> Policies –> Administrative Templates –> Windows Components –> Windows PowerShell –> Turn on Script Execution

    And the line for the customsettings.ini file in MDT that needs added is:
    MachineObjectOU=OU=Name of OU,DC=Domain,DC=com

    I hope this information saves someone else a lot of headaches and time that I had to endure 🙂

    • I almost forgot…DO NOT have spaces in the OU. MDT does not seem to like and it took a bit for me to figure out that that was causing the PC to not join the domain. Use a hyphen(-) or underscore(_) instead.

  • Here’s an odd scenario for you…

    Lets say I have two of the same product, but they are in different folders in PDQ for organizational needs but share the same name. The two deployments are similar, but not exact.

    Calling pdqdeploy.exe to call on a package is pretty vague as you just enter the package name. ‘pdqdeploy.exe getpackagenames’ shows that there are two packages with the same name.
    What happens in this scenario?

    How can I call on a specific package? I looked around but couldn’t find any information on this.

  • Skip that, I think I may have found my answer.
    Please let me know if I’m on the right track though.

    PDQDeploy.exe Deploy -Package “Packages\PackageName”

Your email address will not be published.

Your Name

This site uses Akismet to reduce spam. Learn how your comment data is processed.