Share via


Creating a Package

The following code samples show how to:

C#

Windows PowerShell

C#

Create a submission package from a project

This sample shows how to create a submission package from a project.

//-----------------------------------------------------------------------
// <copyright file="CreateAPackage.cs" company="Microsoft">
//    Copyright © Microsoft Corporation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace Samples
{
    using System;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Collections.Generic;
    using Microsoft.Windows.Kits.Hardware.ObjectModel;
    using Microsoft.Windows.Kits.Hardware.ObjectModel.DBConnection;
    using Microsoft.Windows.Kits.Hardware.ObjectModel.Submission;
    using System.Collections.Specialized;
    using System.IO;

    public class CreateAPackage
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Usage: CreateAPackage.exe [ControllerMachineName] [ProjectName] [PathToDrivers] [SaveFileName]");

            if (args.Length != 4)
            {
                Console.WriteLine("Controller Name, Project Name, Path-to-drivers and SaveFile all need to be supplied as an argument.");
                return;
            }

            // first, connect to the controller
            string controllerName = args[0];
            Console.WriteLine("Connecting to controller : {0}", controllerName);
            ProjectManager manager = new DatabaseProjectManager(controllerName);

            // next, load the project
            string projectName = args[1];
            Console.WriteLine("Opening project : {0}", projectName);
            Project project;
            try
            {
                project = manager.GetProject(projectName);
            }
            catch (ProjectManagerException e)
            {
                Console.WriteLine("could not open project {0} \nError : {1}",
                    projectName,
                    e.Message);
                return;
            }

            string driverPath = args[2];
            Console.WriteLine("adding drivers found at {0}", driverPath);

            string saveFileName = args[3];
            Console.WriteLine("saving package to {0}", saveFileName);

            // the steps to create a package are:
            // 1) create a LogoPackageWriter
            // 2) add drivers
            // 3) [optional] add supplemental content
            // 4) save to disk

            PackageWriter packageWriter = new PackageWriter(project);

            // packaging can be somewhat slow because it may have a lot of files to read, so compress and write
            // the packaging information does have an alerting mechanism
            // this will also be alerted for events when processing drivers
            packageWriter.SetProgressActionHandler(SubmissionCreationProgressHandler);


            // the AddDriver method has this definition.
            //  public bool AddDriver(
            //        string pathToDriver, 
            //        string pathToSymbols, 
            //        ReadOnlyCollection<Target> targets, 
            //        ReadOnlyCollection<string> locales, 
            //        out StringCollection errorMessages,
            //        out StringCollection warningMessages)
            // the path to symbols are optional, and can be null
            // if there is at least one unreferenced file, that will be noted in warningMessages, along with a path to the unreferenced file log
            // the unreferenced file log is a file "{Drive letter}\HLK\JobsWorkingDir\UnreferencedFileLogs\UnreferencedFiles_{timestamp}.txt" generated during every run of AddDriver 
            //
            // there is also a second overloaded AddDriver method with output args for unreferenced file info
            //  public bool AddDriver(
            //        string pathToDriver, 
            //        string pathToSymbols, 
            //        ReadOnlyCollection<Target> targets, 
            //        ReadOnlyCollection<string> locales, 
            //        out StringCollection errorMessages,
            //        out StringCollection warningMessages,
            //        out bool unreferencedFilePresent,
            //        out string unreferencedFileLogPath)
            // like previous method overload, unreferenced file info will also get added to warningMessages (if there is at least 1 unreferenced file)
            
            // each driver package can be associated with one or more targets, and can be from targets in different product instances
            // the possible locales can be retrieved from LogoManager.GetLocaleList()
            // when adding a driver, the driver package is validated that it will be signalable, and additional checks will be performed
            // this means that if this task fails, errorMessages and warningMessages will be filled out to provide information about any
            // problems that are encountered

            // for simplicity, add one driver package as received from the command line, 
            // and associate that with all of the targets in the project
            List<Target> targetList = new List<Target>();
            foreach (ProductInstance pi in project.GetProductInstances())
            {
                targetList.AddRange(pi.GetTargets());
            }

            // also for simplicity, use the first 3 locales returned by GetLocaleList
            List<string> localeList = new List<string>();
            localeList.Add(ProjectManager.GetLocaleList()[0]);
            localeList.Add(ProjectManager.GetLocaleList()[1]);
            localeList.Add(ProjectManager.GetLocaleList()[2]);

            StringCollection errorMessages;
            StringCollection warningMessages;
            bool unreferencedFilePresent;
            string unreferencedFileLogPath;
            
            // call this method
            bool success = packageWriter.AddDriver(driverPath, null, targetList.AsReadOnly(), localeList.AsReadOnly(), out errorMessages, out warningMessages, out unreferencedFilePresent, out unreferencedFileLogPath);
            if (!success)
            {
                Console.WriteLine("Add driver failed to add this driver found at : {0}", driverPath);
                foreach (string message in errorMessages)
                {
                    Console.WriteLine("Error: {0}", message);
                }
            }

            // warnings might not cause the method to fail, but may still be present
            if (warningMessages.Count != 0)
            {
                Console.WriteLine("Add driver found warnings in the package found at : {0}", driverPath);                
                foreach (string message in warningMessages)
                {
                    Console.WriteLine("Warning: {0}", message);

                    // the recommended way of getting the unreferenced file log path is to use the AddDriver overload (the one used above) that includes this value in output args.
                    // nevertheless, no matter which AddDriver overload is used, it is possible to get the unreferenced file log path by parsing the warning messages:
                    //
                    // if(message.StartsWith("There was at least one file in the driver package that was not referenced by an .inf"))
                    // {
                    //     int lastSpace = message.LastIndexOf(' ');
                    //     Console.WriteLine("Unreferenced file log path: {0}", message.Substring(lastSpace+1));
                    // }
                }
            }
            Console.WriteLine("Unreferenced file present: {0}", unreferencedFilePresent); //false if all files are referenced
            Console.WriteLine("Unreferenced file log path: {0}", unreferencedFileLogPath);//the log is still produced even if all files are referenced

            if(unreferencedFilePresent) {
                try
                {
                    // Names of unreferenced files are available in log
                    using (var reader = new StreamReader(unreferencedFileLogPath))
                    {
                        string line;
                        while ((line = reader.ReadLine()) != null && !line.StartsWith("Unreferenced Files:"));// skip to unreferenced file section
                        while ((line = reader.ReadLine()) != null && line.Length>0) { 
                            Console.WriteLine("Unreferenced file: {0}", line.Trim()); 
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Error parsing log {unreferencedFileLogPath}. Details: {ex.Message}");
                }
            }

            // and now call the save as
            // this save as does the bulk of the work
            // Note: it is possible to save a package even with unreferenced files
            packageWriter.Save(saveFileName);

            // now that we're done with writing the package, dispose it to make sure the file handle is closed
            packageWriter.Dispose();         
        }

        public static void SubmissionCreationProgressHandler(PackageProgressInfo info)
        {
            Console.WriteLine("Package progress {0} of {1} : {2}", info.Current, info.Maximum, info.Message);
        }
    }
}

The unreferenced file log ("{Drive letter}\HLK\JobsWorkingDir\UnreferencedFileLogs\UnreferencedFiles_{timestamp}.txt") contains the following information:

  • Project name

  • Log creation timestamp

  • List of unreferenced files

    • If all attached files are referenced, the following text will be included under the "Unreferenced Files" section: "(None, thank you for referencing all attached files!)"
  • List of files referenced by an .inf

Example of log format:

Project Name: Project1
Log Creation: 2025-12-23 15:33:36

Please only include files that are referenced by an .inf in the driver package.
Refer to HLK's Microsoft Learn pages online for more information.

Unreferenced Files:
	driverB.sys
    driverB.txt

Files expected from .inf:
	driverA.cat
	driverA.inf
	drvSubdirectory\driverA.sys

Sign a package using a digital signature stored in a pfx file

This sample shows how to sign a package using a digital signature stored in a pfx file.

namespace HLK
{
    using System;
    using System.Security.Cryptography.X509Certificates;
    using Microsoft.Windows.Kits.Hardware.ObjectModel.Submission;
 
    class Program
    {
        static void Main(string[] args)
        {
            string packageFile = args[0];
            string pfxFile = args[1];

            Console.WriteLine("Enter the password for the pfx file:");

            string password = Console.ReadLine();

            X509Certificate certificate = new X509Certificate(pfxFile, password);

            PackageManager.Sign(packageFile, certificate);
        }
    }
}

PowerShell

Create a submission package from a project

This sample shows how to create a submission package from a project.

$ObjectModel  = [Reflection.Assembly]::LoadFrom($env:WTTSTDIO + "microsoft.windows.Kits.Hardware.objectmodel.dll")
$DbConnection = [Reflection.Assembly]::LoadFrom($env:WTTSTDIO + "microsoft.windows.Kits.Hardware.objectmodel.dbconnection.dll")
$Submission   = [Reflection.Assembly]::LoadFrom($env:WTTSTDIO + "microsoft.windows.Kits.Hardware.objectmodel.submission.dll")
$Submission   = [Reflection.Assembly]::LoadFrom($env:WTTSTDIO + "microsoft.windows.Kits.Hardware.objectmodel.submission.package.dll")

    write-Host "Usage: %SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -file CreateAPackage.ps1 [ControllerMachineName] [ProjectName] [PathToDrivers] [SaveFileName]"

    if ($args.count -ne 4)
    {
        write-host "error: need to provide the controller name, name of the project to package, path to drivers, and save file location "
        return
    }

    $ControllerName = $args[0]
    $projectName = $args[1];
    $driverPath = $args[2];
    $saveFileName = $args[3];
    
# we need to connect to the Server

    if ($ControllerName -eq $null -OR $ControllerName -eq "")
    {
        write-host "Need to supply the controller Name as a parameter to this script"
        return
    }

    if ($projectName -eq $null -OR $projectName -eq "")
    {
        write-host "Need to supply the Project Name as a parameter to this script"
        return
    }

    Write-host "connecting to controller : $ControllerName"
    $Manager = new-object -typename Microsoft.Windows.Kits.Hardware.ObjectModel.DBConnection.DatabaseProjectManager -Args $ControllerName, HLKJobs


# next load the project
    write-host "Opening project :$projectName"
    $project = $Manager.GetProject($projectName)
    if ($project -eq $null)
    {
        write-host "unable to load project `n" $error.ToString()
    }

    write-host "adding drivers found at $driverPath"

    write-host "saving package to $saveFileName"

# the steps needed to create a package are:
# 1) create a LogoPackageWriter
# 2) add drivers
# 3) [optional] add supplemental content
# 4) save to disk

    $packageWriter = new-object -typename Microsoft.Windows.Kits.Hardware.ObjectModel.Submission.PackageWriter -Args $project
   
# the AddDriver method has this definition.
#  public bool AddDriver(
#        string pathToDriver, 
#        string pathToSymbols, 
#        ReadOnlyCollection<Target> targets, 
#        ReadOnlyCollection<string> locales, 
#        out StringCollection errorMessages,
#        out StringCollection warningMessages)
# the path to symbols are optional, and can be null
# if there is at least one unreferenced file, that will be noted in warningMessages, along with a path to the unreferenced file log
# the unreferenced file log is a file "{Drive letter}\HLK\JobsWorkingDir\UnreferencedFileLogs\UnreferencedFiles_{timestamp}.txt" generated during every run of AddDriver
#
# there is also a second overloaded AddDriver method with output args for unreferenced file info
#  public bool AddDriver(
#        string pathToDriver, 
#        string pathToSymbols, 
#        ReadOnlyCollection<Target> targets, 
#        ReadOnlyCollection<string> locales, 
#        out StringCollection errorMessages,
#        out StringCollection warningMessages,
#        out bool unreferencedFilePresent,
#        out string unreferencedFileLogPath)
# like previous method overload, unreferenced file info will also get added to warningMessages (if there is at least 1 unreferenced file)
            
# each driver package can be associated with one or more targets, and can be from targets in different product instances.
# the possible locales can be retrieved from LogoManager.GetLocaleList()
# when adding a driver, the driver package is validated that it will be signal-able, and additional checks will be performed
# this means that if this task fails, the errorMessages and warningMessages will be filled out to provide information about any
# problems encountered

# for simplicity, we are going to add one driver package that we received from the command line, 
# and associate that with all of the targets in the project
    $targetList = New-Object "System.Collections.Generic.List``1[Microsoft.Windows.Kits.Hardware.ObjectModel.Target]"
            
    $Project.GetProductInstances() | foreach {
        $targetlist.AddRange($_.GetTargets())
        }            
            
# also for simplicity, we are going to use the first 3 locales returned by the GetLocaleList.
    $localeList = New-Object "System.Collections.Generic.List``1[System.string]"
    $localeList.Add([Microsoft.Windows.Kits.Hardware.ObjectModel.ProjectManager]::GetLocaleList()[0])
    $localeList.Add([Microsoft.Windows.Kits.Hardware.ObjectModel.ProjectManager]::GetLocaleList()[1])
    $localeList.Add([Microsoft.Windows.Kits.Hardware.ObjectModel.ProjectManager]::GetLocaleList()[2])
            
    $errorMessages = New-Object "System.Collections.Specialized.StringCollection"
    $warningMessages = New-Object "System.Collections.Specialized.StringCollection"
    $unreferencedFileLogPath = ""
    $unreferencedFilePresent = New-Object "System.Boolean"

# go ahead and call this API
    if ($packageWriter.AddDriver($driverPath, [System.Management.Automation.Language.NullString]::Value, $targetList.AsReadOnly(), $localeList.AsReadOnly(), [ref] $errorMessages, [ref] $warningMessages, [ref] $unreferencedFilePresent, [ref] $unreferencedFileLogPath) -eq $false)
    {
        Write-Host "Add driver failed to add this driver found at : $driverPath"
        foreach ($msg in $errorMessages)
        {
            Write-Host "Error: $msg" 
        }
    }

    # warnings might not cause the method to fail, but may still be present
    if ($warningMessages.Count -ne 0)
    {
        Write-Host "Add driver found warnings in the package found at : $driverPath"
        foreach ($msg in $warningMessages)
        {
            Write-Host "Warning: $msg"

            # the recommended way of getting the unreferenced file log path is to use the AddDriver overload (the one used above) that includes this value in output args.
            # nevertheless, no matter which AddDriver overload is used, it is possible to get the unreferenced file log path by parsing the warning messages:
            #
            # if ($msg -like 'There was at least one file in the driver package that was not referenced by an .inf*') {
            #   $lastSpace = $msg.LastIndexOf(' ')
            #   $unreferencedFileLogPath = $msg.Substring($lastSpace + 1)
            #   Write-Host "Unreferenced file log path: $unreferencedFileLogPath"
            # }
        }
    }

    Write-Host "Unreferenced file present: $unreferencedFilePresent" # false if all files are referenced
    Write-Host "Unreferenced file log path: $unreferencedFileLogPath" # the log is still produced even if all files are referenced

    if ($unreferencedFilePresent) {
        try {
            # Names of unreferenced files are available in log
            $lines = Get-Content -Path $unreferencedFileLogPath -ErrorAction Stop

            # find the header line index
            $startIndex = -1
            for ($i = 0; $i -lt $lines.Count; $i++) {
                if ($lines[$i] -match '^Unreferenced Files:') { $startIndex = $i; break }
            }

            if ($startIndex -ge 0) {
                for ($j = $startIndex + 1; $j -lt $lines.Count; $j++) {
                    if ([string]::IsNullOrWhiteSpace($lines[$j])) { break }
                    Write-Host "Unreferenced file: $($lines[$j].Trim())"
                }
            }
        }
        catch {
            Write-Host "Error parsing log $unreferencedFileLogPath. Details: $($_.Exception.Message)"
        }
    }
                                   
# Since packaging can be somewhat slow, as it may have a lot of files to read, compress and write
# the packaging information does have an alerting mechanism
#  This is not implemented in the PowerShell example, but it is implemented in the C# version
    # packageWriter.SetProgressActionHandler(SubmissionCreationProgressHandler);

# and now call the save as
# this save as does the bulk of the work
# Note: it is possible to save a package even with unreferenced files
    $packageWriter.Save($saveFileName);

# now that we're done with writing the package, dispose it to make sure the file handle is closed
    $packageWriter.Dispose();
        

       

Sign a package using a digital signature stored in a pfx file

This sample shows how to sign a package using a digital signature stored in a pfx file.

# Load DLLs.
$ObjectModel = [Reflection.Assembly]::LoadFrom($env:WTTSTDIO + "Microsoft.Windows.Kits.Hardware.objectmodel.dll")
$ObjectModel = [Reflection.Assembly]::LoadFrom($env:WTTSTDIO + "Microsoft.Windows.Kits.Hardware.objectmodel.submission.dll")

    Write-Host "Usage: %SystemRoot%\System32\WindowsPowershell\v1.0\powershell.exe -file sign_package.ps1 <<Full Path To Package>> <<Full Path to PFX>>"

# The package to sign.
    $packageFile = $args[0]

# The pfx file to use.
    $pfxfile = $args[1]

# The password for the pfx file.
    $pfxpassword = Read-host "Please enter the password for the pfx file" -AsSecureString

# Load the certificate.
    $cert = new-object -typename System.Security.Cryptography.X509Certificates.X509Certificate($pfxfile, $pfxpassword)

# Sign the package.
    [Microsoft.Windows.Kits.Hardware.ObjectModel.Submission.PackageManager]::Sign($packageFile, $cert)

Note

When signing some (large) packages using PowerShell, you might see an exception such as "Unable to determine the identity of domain". When this exception occurs, please use the managed API (see https://msdn.microsoft.com/library/windows/hardware/jj123504.aspx#BKMK_CS_SignPackage) as a work-around."

Determine for what operating systems a driver passes the signing checks

This sample shows how to determine for what operating systems a driver passes the signing checks.

# Load DLLs.
$ObjectModel = [Reflection.Assembly]::LoadFrom($env:WTTSTDIO + "Microsoft.Windows.Kits.Hardware.objectmodel.dll")
$ObjectModel = [Reflection.Assembly]::LoadFrom($env:WTTSTDIO + "Microsoft.Windows.Kits.Hardware.objectmodel.submission.dll")

    Write-Host "Usage: %SystemRoot%\System32\WindowsPowershell\v1.0\powershell.exe -file check_downlevel_os.ps1 <<Full Path To Package>>"

# The package file.
    $packageFile = $args[0]

# Create a package manager.
    $Manager = new-object -typename Microsoft.Windows.Kits.Hardware.ObjectModel.Submission.PackageManager -Args $packageFile

    $Manager.GetProjectNames() | foreach {
        Write-Host Examining Project $_
        $Project = $Manager.GetProject($_)
        $Project.GetProductInstances() | foreach {
            $_.GetTargetFamilies() | foreach {
                $_.GetTargets() | foreach {
                    Write-Host Examining Target $_.Name
                    $_.GetDrivers() | foreach {
                        Write-Host Driver is signable for the following platforms
                        Write-Host $_.SignabilityResults
                    }
                }
            }
        }
    }

# Clean up.
    $Manager.Dispose()

Verify a digital certificate

This sample shows how to verify a digital certificate.

# Load DLLs.
$ObjectModel = [Reflection.Assembly]::LoadFrom($env:WTTSTDIO + "Microsoft.Windows.Kits.Hardware.objectmodel.dll")
$ObjectModel = [Reflection.Assembly]::LoadFrom($env:WTTSTDIO + "Microsoft.Windows.Kits.Hardware.objectmodel.submission.dll")

    Write-Host "Usage: %SystemRoot%\System32\WindowsPowershell\v1.0\powershell.exe -file verify_certificate.ps1 <<Full Path To Package>>"

# The package file.
    $packageFile = $args[0]

# Create a package manager.
    $Manager = new-object -typename Microsoft.Windows.Kits.Hardware.ObjectModel.Submission.PackageManager -Args $packageFile

# Write out the certificate object.
    Write-host $Manager.Certificate

# Clean Up.
    $Manager.Dispose()