Tuesday, May 27, 2008

Installing device drivers without the device being present

Having completed a number of SOE unattended installations, I have had reason to be able to install device drivers without the device being present. Attempts to install drivers without the device present using well known driver installation tools such as devcon.exe and dpinst.exe, failed to load the device drivers.

During investigation of the process I reviewed the device installation log file, %SystemRoot%\setupapi.log. In hindsight, the log file is quite aptly named in that functions within the setupapi.dll are called to install devices during windows setup and driver installation programs. This was shown by log entries from the devcon and dpinst programs.

Searches for setupapi.dll eventually showed that the SetupCopyOEMInf function is used to install the devices. Which in turn led to Pyron's SetupCopyOEMInf.exe utility. Whilst the utility is fantastic and performs device installation flawlessly, I was after something that could be manipulated if required.

Enter Powershell!

Utilising managed API calls, I was able to write a powershell script that calls the SetupCopyOEMInf function whilst maintaining the flexibility that scripting offers. Whilst by no means the right solution for everyone, this approach further demonstrates the capabilities of powershell.

In summary, the script installs the device drivers without the device being present.

During a standard user session, the device can be plugged in and the device drivers are loaded without prompting for Administrator credentials.

[string] $DriverDir = $(throw "Please specify a directory of drivers to process")

$Scripts = Split-Path $Myinvocation.Mycommand.Path
# Copy a Driver to the OEM path (Install driver without the device being present)
# References
# http://monadblog.blogspot.com/2005/12/calling-win32-api-functions-through.html
# http://www.pinvoke.net/default.aspx/setupapi.SetupCopyOEMInf
# Set Error Handling
$ErrorActionPreference = "SilentlyContinue"
$Me = "SetupCopyOEMInf"
$provider = new-object Microsoft.VisualBasic.VBCodeProvider

$params = new-object System.CodeDom.Compiler.CompilerParameters
$params.GenerateInMemory = $True
$refs = "System.dll","Microsoft.VisualBasic.dll"
# Find all the .inf files in the specified directory

$TaskDesc = "Locate INF Files"
$TempDir = Get-ChildItem $DriverDir -filter '*.inf' -recurse
If ($? -eq $False) {
Write-Host "Invalid directory $($DriverDir)"
Exit 1

$txtCode = @'
Class CopyOEMInf
Private Declare Function SetupCopyOEMInf Lib "setupapi.dll" Alias "SetupCopyOEMInfA" (ByVal SourceInfFileName As String, ByVal OEMSourceMediaLocation As String, ByVal OEMSourceMediaType As Long, ByVal CopyStyle As Long, ByVal DestinationInfFileName As String, ByVal DestinationInfFileNameSize As Long, ByRef RequiredSize As Long, ByVal DestinationInfFileNameComponent As String) As Long

Public Function Main(ByVal sInf As String, ByVal sDir As String) As Long
Const SP_COPY_NEWER As Integer = &H4
Dim lngResult As Long

Dim ret2 As String
Dim ret3 As String

' Install the driver'
lngResult = SetupCopyOEMInf(sInf, sDir, 1, SP_COPY_NEWER, ret2, 255, 255, ret3)
End Function
end class


$cr = $provider.CompileAssemblyFromSource($params, $txtCode)
if ($cr.Errors.Count) {
$codeLines = $txtCode.Split("`n");

foreach ($ce in $cr.Errors) {
Write-Host "Code compilation error, line $($ce.Line - 1)"
write-host "Error: $($codeLines[$($ce.Line - 1)])"
write-host $ce
Throw "INVALID DATA: Errors encountered while compiling code"

$mAssembly = $cr.CompiledAssembly

$i = $mAssembly.CreateInstance("CopyOEMInf")
# Process each inf file found

$TempDir ¦ foreach {
Write-Host "Installing Driver $($_.Get_FullName().ToString())"

$r = $i.Main($_.Get_FullName().ToString(), $_.Get_Directory().ToString())

Troubleshooting Device Installation with the SetupAPI Log File
Pyron setupcopyoeminf

Thanks goes to Wayne for introducing the concept and code snippets for compiling managed code from unmanaged scripts.


Neil said...

Hi , i tried your script however

when debugging it it errors
on the foreach loop that is supposed to process each inf file found.

Im unfamiliar with the powershell syntax , so hope you could take another look at the script

it appears that you haven't completed the foreach loop

PS C:\Documents and Settings\nmackinnon\Desktop\install driver without device present> C:\Documents and Settings\nmackinnon\Desktop\install driver without device present\Installwithdevicenotpresent.ps1
Unexpected token 'foreach' in expression or statement.
At C:\Documents and Settings\nmackinnon\Desktop\install driver without device present\Installwithdevic
enotpresent.ps1:53 char:19
+ $TempDir foreach <<<< {
+ CategoryInfo : ParserError: (foreach:String) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : UnexpectedToken

CmB said...

Hi Neil,

My apologies for not responding sooner!

It seems that in the upload, the script is missing the pipe (|) symbol.

I have updated the post accordingly.


aliah said...

When you are into computers, driver installation is all easy task. i like the way you have explained the whole process, did get good amount of information.
Abu Dhabi Car Battery