Skip to content

Instantly share code, notes, and snippets.

@rileyz
Last active February 19, 2020 09:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rileyz/464175e3bb96f1b67dfc to your computer and use it in GitHub Desktop.
Save rileyz/464175e3bb96f1b67dfc to your computer and use it in GitHub Desktop.
<#
.SYNOPSIS
Reusable script for staging many device driver packages to the Drivers Store.
.DESCRIPTION
Intended Use
This script was produced to stage device drivers for an App-V package. Because device drivers
or drivers in general can't be virtualised, a method is required to stage the drivers to the
operating system, what we call in App-V colloquial terms, outside of the bubble.
Example
Action this script using the App-V DeploymentConfig, using the child elements AddPackage and/or
RemovePackage.
The driver package can either be located on the VFS or added as script collateral in the form
of a zip archive.
The script supports the importing and removal of certificates from the Trusted Publishers
certificate store. This is workaround to enable silent staging of Authenticode-Signed
Non-WHQL-Class driver packages. To utilise this feature, store your certificate/s at the
base directory of the drivers and prefix the certificate with 'TrustedPublisher'.
eg. TrustedPublisher-ContosoToasterHardware.cer
The mandatory parameters are as follows.
-DriverSource Token, UNC or relative path to driver source.
-LogName Name of the log file.
The optional parameters are as follows.
-HideARP Will not add entry to Programs and Features.
-Remove Will remove the driver package from the Driver Store.
* To stage a driver package to the Driver Store, where the driver package is located on the VFS.
Manage-Drivers.ps1 -DriverSource "..\Root\VFS\ProgramFilesX86\DriverFolder" -LogName "Example.log"
* To stage a driver package to the Driver Store, where the driver package is located on the VFS
in a form of a zip archive.
Manage-Drivers.ps1 -DriverSource "[{ProgramFilesX86}]DriverFolder\DriverArchive.zip" -LogName "Example.log"
* To stage a driver package to the Driver Store, where the driver package is script collateral in
the form of a zip archive.
Manage-Drivers.ps1 -DriverSource "DriverArchive.zip" -LogName "Example.log"
* To stage a driver package to the Driver Store and not add the 'Program and Features' entry.
Manage-Drivers.ps1 -DriverSource "DriverArchive.zip" -LogName "Example.log" -HideARP
* To remove a driver package from the Driver Store, where the driver package is located on
the VFS.
Manage-Drivers.ps1 -DriverSource "..\Root\VFS\ProgramFilesX86\DriverFolder" -LogName "Example.log" -Remove
* To remove a driver package to the Driver Store, where the driver package is located on the VFS
in a form of a zip archive.
Manage-Drivers.ps1 -DriverSource "[{ProgramFilesX86}]DriverFolder\DriverArchive.zip" -LogName "Example.log" -Remove
* To remove a driver package from the the Driver Store, where the driver package is script
collateral in the form of a zip archive.
Manage-Drivers.ps1 -DriverSource "DriverArchive.zip" -LogName "Example.log" -Remove
About
I needed a script to stage driver packages into the Driver Store from App-V, after much
research I couldn't find anything of great help on the interwebs. The only information that
came close to resolving my issue was Nicke Kallen's blog "App-V 5 and Drivers" at
www.applepie.se. This was close to the solution, my desire was to action it without the
use of Windows Installer packages but rather the free utility DPIsnt. This allows for lower
administration overhead whist enabling anyone without access to packaging software, such
as Adminstudio, to stage drivers in combination with App-V.
A thank you to Anton Burgess for the tip on driver certificates.
Known Defects/Bugs
* Single quotes when used to pass arguments will cause the script to defect. Please use double
quotes to pass the arguments.
* A backslash at the end of the DriverSource folder path will cause the script to fatally error
and not run, no log being created.
* Error checking and trapping has been provided but could be improved.
* If the script Log contains no "END OF LOGGING" line, increase the timeout in the
DeploymentConfig to allow more time for the script to complete.
* Resolved! ScriptRunner.exe released in App-V 5.1 is a 32-bit process, this causes PowerShell to be
launched as a 32-bit proccess, the knock on effect is the architecture detection will be
incorrect. Workaround, do not use ScriptRunner.exe to execute this script.
Code Snippet Credits
* http://www.howtogeek.com/tips/how-to-extract-zip-files-using-powershell
* http://stackoverflow.com/questions/7834656/create-log-file-in-powershell
* http://www.networkworld.com/article/2346838/microsoft-subnet/how-to-read-certificates-and-crls-using-powershell.html
* http://blogs.technet.com/b/heyscriptingguy/archive/2014/08/31/powertip-using-powershell-to-determine-if-path-is-to-file-or-folder.aspx
Version History
1.2 16/07/2016
Improved architecture detection method when the parent process is 32-bit (ScriptRunner.exe) on
a 64-bit operating system. Grammar and spelling corrections.
1.1 12/05/2016
Bugfixes.
1.0 25/01/2015
Initial release.
Copyright & Intellectual Property
Feel to copy, modify and redistribute, but please pay credit where it is due.
Feed back is welcome, please contact me on LinkedIn.
.LINK
Author:.......http://www.linkedin.com/in/rileylim
Source Code:..https://gist.github.com/rileyz/464175e3bb96f1b67dfc
Article:......http://www.itninja.com/blog/view/app-v-5-and-drivers
.EXAMPLE
Staging a driver package where it is located on the VFS.
<AddPackage>
<Path>powershell.exe</Path>
<Arguments>-ExecutionPolicy ByPass -File Manage-Drivers.ps1 -DriverSource "..\Root\VFS\ProgramFilesX86\DriverFolder" -LogName "Example.log"</Arguments>
...
.EXAMPLE
Staging a driver package where it is located on the VFS in a form of a zip archive.
<AddPackage>
<Path>powershell.exe</Path>
<Arguments>-ExecutionPolicy ByPass -File Manage-Drivers.ps1 -DriverSource "[{ProgramFilesX86}]DriverFolder\DriverArchive.zip" -LogName "Example.log"</Arguments>
.EXAMPLE
Staging a driver package where it is script collateral in the form of a zip archive.
<AddPackage>
<Path>powershell.exe</Path>
<Arguments>-ExecutionPolicy ByPass -File Manage-Drivers.ps1 -DriverSource "DriverArchive.zip" -LogName "Example.log"</Arguments>
...
.EXAMPLE
Staging a driver package and not add the 'Program and Features' entry.
<AddPackage>
<Path>powershell.exe</Path>
<Arguments>-ExecutionPolicy ByPass -File Manage-Drivers.ps1 -DriverSource "DriverArchive.zip" -LogName "Example.log" -HideARP</Arguments>
...
.EXAMPLE
Remove a driver package where it is located on the VFS.
<RemovePackage>
<Path>powershell.exe</Path>
<Arguments>-ExecutionPolicy ByPass -File Manage-Drivers.ps1 -DriverSource "..\Root\VFS\ProgramFilesX86\DriverFolder" -LogName "Example.log" -Remove</Arguments>
...
.EXAMPLE
Remove a driver package where it is located on the VFS in a form of a zip archive.
<AddPackage>
<Path>powershell.exe</Path>
<Arguments>-ExecutionPolicy ByPass -File Manage-Drivers.ps1 -DriverSource "[{ProgramFilesX86}]DriverFolder\DriverArchive.zip" -LogName "Example.log" -Remove</Arguments>
.EXAMPLE
Remove a driver package where it is script collateral in the form of a zip archive.
<RemovePackage>
<Path>powershell.exe</Path>
<Arguments>-ExecutionPolicy ByPass -File Manage-Drivers.ps1 -DriverSource "DriverArchive.zip" -LogName "Example.log" -Remove</Arguments>
...
#>
Param ([Parameter(Mandatory=$True)][String]$DriverSource,
[Parameter(Mandatory=$True)][String]$LogName,
[Switch] $HideARP,
[Switch] $Remove)
# Function List ###################################################################################
Function LogWrite {Param ([String] $LogLine,
[Switch] $EndOfLog)
If ($EndOfLog -eq $True) {Add-content $LogFile -value 'INFO: END OF LOGGING'
Add-content $LogFile -value ''}
Else {Add-content $LogFile -value $LogLine}}
Function WindowsTempCleanup {If ($IsDriverSourceAFolder -eq $False) {Remove-Item $WindowsTempFolder -Force -Recurse
LogWrite "EVENT: Removed temporary working folder $WindowsTempFolder"}}
#<<< End Of Function List >>>
# Setting up housekeeping #########################################################################
$LogFile = "$env:systemroot\Temp\$LogName"
$LogDate = Get-Date -Format 'dd MMMM, yyyy, HH:mm.'
$DPInst32 = '\DPInst-x86.exe' #Please ensure the DPInst executable has the same name as
$DPInst64 = '\DPInst-amd64.exe' #in the DriverSource.
$WithThisErrorCode = 0 #Graceful exit: 0. Fatal error: 1, this will halt Add-AppVPackage
#if using the standard xml attributes.
#eg <Wait RollbackOnError="true" Timeout="30"/>.
#<<< End of Setting up housekeeping >>>
# Start of script work ############################################################################
LogWrite 'INFO: ****************************************'
LogWrite 'INFO: Manage Drivers Log'
LogWrite 'INFO: Author: http://www.linkedin.com/in/rileylim'
LogWrite 'INFO: Source Script: https://gist.github.com/rileyz/464175e3bb96f1b67dfc'
LogWrite "INFO: $LogDate"
LogWrite 'INFO: Running with the following parameters:'
LogWrite "INFO: DriverSource: $DriverSource"
LogWrite "INFO: Hide ARP: $HideARP | Remove drivers: $Remove"
LogWrite 'INFO: ****************************************'
If (((Get-WmiObject Win32_OperatingSystem).OSArchitecture).Contains('32') -eq $True) {$Architecture32Query++}
Else{$Architecture64Query++}
If ((Test-Path $ENV:WinDir\SysWOW64) -eq $True) {$Architecture64Query++}
Else{$Architecture32Query++}
Switch ([Environment]::Is64BitOperatingSystem) {True {$Architecture64Query++}
False {$Architecture32Query++}
Default {LogWrite 'INFO: Bitness check via a .Net 4 property was not run because it is not installed/enabled.'}}
If ($Architecture32Query -gt $Architecture64Query) {$Architecture = '32-bit'
LogWrite "INFO: Bitness check passed at $Architecture32Query/3 for 32-bit."}
Else{$Architecture = '64-bit'
LogWrite "INFO: Bitness check passed at $Architecture64Query/3 for 64-bit."}
LogWrite "INFO: The operating system architecture is $Architecture."
Try {$IsDriverSourceAFolder = (Get-Item $DriverSource -ErrorAction Stop) -is [System.IO.DirectoryInfo]}
Catch {LogWrite "ERROR: The DriverSource is not a valid folder or file!"
LogWrite 'INFO: Please ensure your relative path reference is correct from the VFS Scripts folder,'
LogWrite 'INFO: or use a App-V token path.'
LogWrite -EndOfLog
Exit $WithThisErrorCode}
If ($IsDriverSourceAFolder -eq $False) {$WindowsTempFolder = "$env:systemroot\Temp\" + [guid]::NewGuid()
$DriverZipPath = Resolve-Path -Path $DriverSource
$DriverSource = $WindowsTempFolder
LogWrite "INFO: Full path to zip file is $DriverZipPath"
$Shell = New-Object -ComObject shell.application
$Zip = $Shell.NameSpace("$DriverZipPath")
New-Item -ItemType Directory -Force -Path $WindowsTempFolder
LogWrite "EVENT: Created temporary working folder $WindowsTempFolder"
Foreach ($Item in $Zip.items()){$Shell.Namespace("$WindowsTempFolder").copyhere($Item)} ##not working
LogWrite 'EVENT: Unpacking of zip file is complete.'}
If ($Architecture -eq '32-bit') {$DPInstForThisArchitecture = $DriverSource + $DPInst32}
Else {$DPInstForThisArchitecture = $DriverSource + $DPInst64}
If ((Test-Path $DPInstForThisArchitecture ) -eq $False) {LogWrite 'ERROR: Can not find DPIsnt executable!'
LogWrite 'INFO: Please ensure the executable has the same file name as in the scripts housekeeping section.'
WindowsTempCleanup
LogWrite -EndOfLog
Exit $WithThisErrorCode}
LogWrite "EVENT: Driver Package Installer (DPInst) is valid and set to $DPInstForThisArchitecture"
If ((Test-Path "$DriverSource\*.inf") -eq $False) {LogWrite 'ERROR: Can not find any Setup Information (.inf) files types!'
LogWrite 'INFO: Please ensure the driver Setup Information (.inf) files are at the root of the DriverSource.'
WindowsTempCleanup
LogWrite -EndOfLog
Exit $WithThisErrorCode}
LogWrite "EVENT: Setup Information (.inf) file types have been found, we have something to process."
If ($Remove -eq $False) {LogWrite 'INFO: Starting driver injection...'
If ($HideARP -eq $True) {$SwitchHideARP = '/SA'
LogWrite 'EVENT: Hide Programs and Features (Add/Remove Programs) entries has been enabled.'
LogWrite 'INFO: By default the script allows DPInst to add entries to Programs and Features.'}
Get-ChildItem -Name "$DriverSource\TrustedPublisher*.cer" | ForEach-Object {$Certificate = Resolve-Path -Path "$DriverSource\$_"
LogWrite "EVENT: Found and processing certificate: $Certificate"
&Certutil.exe -addstore -f "TrustedPublisher" "$Certificate" | Out-Null
LogWrite "EVENT: CertUtil exit code was $LASTEXITCODE"}
If ((Test-Path "$DriverSource\dpinst.xml") -eq $True) {LogWrite 'INFO: Found dpinst.xml, be aware this file may affect your desired settings.'}
LogWrite 'EVENT: Starting DPInst to process drivers.'
Start-Process -FilePath "$DPInstForThisArchitecture" -ArgumentList "/Q /SE /F $SwitchHideARP" -Wait
LogWrite 'INFO: Driver injection complete, please check DPInst log for more details.'
WindowsTempCleanup
LogWrite -EndOfLog}
Else {LogWrite 'INFO: Starting driver removal...'
Get-ChildItem -Name "$DriverSource\*.inf" | ForEach-Object {$INF = Resolve-Path -Path "$DriverSource\$_"
LogWrite "EVENT: Processing Driver Store removal of $INF"
$Args = "/U " + '"' + $INF + '"' + " /Q"
Start-Process -FilePath "$DPInstForThisArchitecture" -ArgumentList "$Args" -Wait}
LogWrite 'INFO: Driver removal complete, please check DPInst log for more details.'
Get-ChildItem -Name "$DriverSource\*.cer" | ForEach-Object {$Certificate = Resolve-Path -Path "$DriverSource\$_"
$Shell = New-Object System.Security.Cryptography.X509Certificates.X509Certificate
$Shell.Import("$Certificate")
$CertificateSerialNumber = $Shell.GetSerialNumberString()
LogWrite "EVENT: Found and processing certificate removal: $Certificate"
LogWrite "EVENT: Serial number: $CertificateSerialNumber"
&Certutil.exe -delstore "TrustedPublisher" "$CertificateSerialNumber" | Out-Null
LogWrite "EVENT: CertUtil exit code was $LASTEXITCODE"}
WindowsTempCleanup
LogWrite -EndOfLog}
#<<< End of script work >>>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment