PackageStack is a collection of projects for generating Windows Provisioning Packages (.ppkg). These packages are a quick and easy way to configure devices running Windows without deploying a new image.
- Truly Cross-Platform: Generate Windows provisioning packages from any major OS, including macOS and Linux. You are no longer tied to a Windows-only workstation.
- No Windows ADK Required: Avoid the large and complex installation of the Windows Assessment and Deployment Kit (ADK). PackageStack is self-contained and lightweight.
- Flexible Usage: Consume it the way you want. Use it as a local PowerShell module for simple scripting, or deploy it as a Docker container or serverless Azure Function for a robust, scalable web API.
- Automation-First Design: With both a PowerShell module and a full web API, PackageStack is designed to be easily integrated into your CI/CD pipelines and automated infrastructure workflows.
- Modern and Open Source: Built on .NET 8 and licensed under the permissive MIT license, PackageStack is a free, modern, and transparent solution that you can trust and extend.
- Supported Features
- Usage
- Output Options
- Deployment Options
- Installing the PowerShell Module
- OpenAPI Specification
- API Examples
- PowerShell Cmdlet Reference
New-PackageStackAzureConfigurationNew-PackageStackComputerConfigurationNew-PackageStackOOBEConfigurationNew-PackageStackPackageConfigurationNew-PackageStackPackageFileConfigurationNew-PackageStackProvisioningCommandConfigurationNew-PackageStackUserConfigurationNew-PackageStackWLANSettingConfigurationNew-PackageStackProvisioningPackage
- Applying the Provisioning Package
- Contributing
- License
- AI and Large Language Model Usage
PackageStack currently supports a base set of provisioning settings to streamline your device setup process.
- Local User Creation: Create local user accounts with specified usernames and passwords.
- Azure AD Join: Seamlessly join devices to Azure Active Directory.
- Active Directory Join: Join devices to a traditional Active Directory domain.
- Set Hostname: Customize the device's computer name.
- Wireless Networks: Pre-configure Wi-Fi network profiles for automatic connection.
- Out-of-Box Experience (OOBE): Customize the OOBE by skipping setup pages to create a faster, more streamlined first-time user experience.
- Provisioning Commands: Execute custom commands and scripts during the provisioning process for advanced configurations.
PackageStack offers flexible usage options to fit your workflow.
The core of PackageStack is a PowerShell module that runs on PowerShell 7.4 or newer. It can be imported into your existing scripts to programmatically build provisioning packages.
To integrate with cloud-native workflows, you can deploy PackageStack as an Azure Function or Docker Container. This approach exposes a web API, allowing you to generate provisioning packages by sending HTTP requests.
Both the PowerShell module and the API provide flexible options for how the final provisioning package is delivered. You can choose the format that best suits your workflow.
This option provides the generated .ppkg as a direct file.
- When to use it: This is the most straightforward option and is ideal for local use. If you are running a script on your machine and want to save the package directly to the filesystem, or if you are using an API client to download the file, this is the best choice.
This option returns the content of the .ppkg file encoded as a single Base64 text string.
- When to use it: This is useful for automation scenarios where you need to embed the package data within a larger script, JSON payload, or another text-based format. It avoids the need to handle binary file transfers, which can simplify integration with certain systems.
This option uploads the generated .ppkg to an Azure Blob Storage container and returns a temporary, secure Shared Access Signature (SAS) URL.
- When to use it: This is the most powerful option for cloud-based or distributed workflows. Instead of transferring the entire file, you receive a lightweight URL that can be easily passed to other services, devices, or users.
Requirements: To use this feature, you must configure an environment variable named AzureWebJobsStorage set to the connection string of your Azure Storage account.
To use this option with the Docker container, you must set the AzureWebJobsStorage environment variable in the docker-compose.yaml file.
# ...
environment:
AzureWebJobsStorage: "DefaultEndpointsProtocol=https...your-connection-string"
# ...If deploying the API as an Azure Function, you should not need to make any changes. The function will automatically detect the environment variable associated with the Azure Function and use it to upload the package to Azure Blob Storage.
PackageStack can be deployed in a variety of ways. The following sections provide more information about each option.
The project includes a file to make it easy to run the PackageStack API using Docker Compose. To start the service, download the compose.yaml file to a local directory and run the following command:
docker-compose up -dThis will pull the latest craysiii/packagestack image from Docker Hub and start the container in detached mode. The API will be available on port 8080. You can change the port by updating the ports section of the compose.yaml file.
The project is designed for easy deployment to Microsoft Azure as a serverless Function App. Before you can publish the code, you need to create the necessary resources in your Azure subscription.
You can create the required resources using the Azure Portal:
- Create a Resource Group: Start by creating a new Resource Group to hold all the related resources for this service.
- Create a Storage Account: Azure Functions requires a storage account to operate. Create a general-purpose storage account within your new resource group. This account will also be used to store the provisioning packages if you use the SasUrl output option.
- Create a Function App: This is the core resource that will host your code. When creating the Function App, make sure you configure the following:
- Publish: Select
Code. - Runtime stack: Select
.NET. - Version: Choose the appropriate .NET version for the project.
- Operating System: Select "Linux".
- Plan type: A "Consumption (Serverless)" plan is recommended to start, as you only pay for what you use.
- Link the Function App to the Storage Account you created earlier.
- Publish: Select
Once the infrastructure is deployed, you will need to publish the PackageStack.AzureFunction project to the newly created Function App. This can be done in several ways:
- From an IDE: Use the built-in publishing features in Visual Studio or JetBrains Rider.
- Azure CLI: Use the az functionapp deployment source config-zip command to deploy the published project.
- CI/CD: Set up a CI/CD pipeline using GitHub Actions or Azure DevOps to automatically build and deploy the function whenever changes are pushed to your repository.
The PackageStack module can be installed on any system that meets the minimum requirements, allowing you to generate provisioning packages from your preferred operating system.
- Operating System: Windows, macOS, or Linux
- PowerShell: Version 7.4 or newer
The module can be installed directly from the PowerShell Gallery.
- Open a PowerShell 7.4 (or newer) terminal.
- Run the following command:
Install-Module -Name PackageStackOnce installed, you can import the module into your session using Import-Module PackageStack to begin using the cmdlets.
The full OpenAPI specification for the PackageStack API is available for interactive viewing. This can be helpful for understanding the available endpoints, request bodies, and response schemas in detail.
You can view the latest specification from the main branch by visiting the following link:
The following examples demonstrate how to send requests to the web API exposed by the Docker container or Azure Function. The endpoint for creating a package is /api/NewProvisioningPackage.
This example sets the computer name of the device.
Request Body
{
"return_type": "Base64",
"package_config": {
"id": "a2a58817-6916-4d69-92d6-2358eed33c82",
"name": "Set-Hostname",
"version": "1.0",
"owner_type": "ITAdmin"
},
"computer_account": {
"computer_name": "MyNewPC-%RAND:5%"
}
}This example joins the device to Azure Active Directory using a Bulk Primary Refresh Token (BPRT).
Request Body
{
"return_type": "Base64",
"package_config": {
"id": "f8c859c3-722c-4ad8-a3f2-8e6f51c72a15",
"name": "AzureAD-Join",
"version": "1.0",
"owner_type": "ITAdmin"
},
"azure": {
"bprt": "your-bulk-primary-refresh-token"
}
}This example creates a new local user account and adds it to the Administrators group.
Request Body
{
"return_type": "Base64",
"package_config": {
"id": "9b1d9bc4-3d92-4f3d-8e4b-9d413e1e6b2c",
"name": "Create-Local-Admin",
"version": "1.0",
"owner_type": "ITAdmin"
},
"local_users": [
{
"username": "localadmin",
"password": "AComplexPassword!",
"group": "Administrators"
}
]
}This example downloads the Zoom installer and executes it silently.
Request Body
{
"return_type": "Base64",
"package_config": {
"id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"name": "Install-Zoom",
"version": "1.0",
"owner_type": "ITAdmin"
},
"provisioning_commands": [
{
"command_line": "msiexec.exe /i \"ZoomInstaller.msi\" /quiet /qn /norestart",
"command_file": {
"name": "ZoomInstaller.msi",
"url": "https://zoom.us/client/latest/ZoomInstallerFull.msi?archType=x64"
}
}
]
}This section provides a detailed reference for each cmdlet included in the PackageStack module.
Creates a configuration object for joining a device to Azure Active Directory.
Parameters
-BPRT <String>: (Required) The Bulk Primary Refresh Token for Azure AD.-Authority <String>: (Optional) The authority server URL. Defaults tohttps://login.microsoftonline.com/common.
Example
$azureConfig = New-PackageStackAzureConfiguration -BPRT "your-bulk-primary-refresh-token"Creates a configuration object for setting the device hostname or joining it to an Active Directory domain.
Parameters
-LocalComputerName <String>: Sets the device's local hostname. Supports the use of%RAND:x%for a random string ofxdigits.-DomainComputerName <String>: Sets the hostname for a domain-joined device.-DomainName <String>: The fully qualified domain name to join (e.g.,corp.contoso.com).-Account <String>: The username of a domain account with permissions to join devices to the domain.-Password <String>: The password for the domain join account.-OrganizationalUnit <String>: (Optional) The distinguished name of the OU to place the computer object in (e.g.,OU=Workstations,DC=corp,DC=contoso,DC=com).
Examples
Set a local hostname:
$computerConfig = New-PackageStackComputerConfiguration -LocalComputerName "DESKTOP-%RAND:5%"Join a device to a domain:
$computerConfig = New-PackageStackComputerConfiguration -DomainComputerName "CORP-%RAND:5%" -DomainName "corp.contoso.com" -Account "corp\join-account" -Password "AComplexPassword!"Creates a configuration object for customizing the Windows Out-of-Box Experience (OOBE).
Parameters
-EnableCortanaVoice <Boolean>: (Optional) Enables or disables Cortana's voice during OOBE.-HideOOBE <Boolean>: (Optional) Skips most of the OOBE setup screens for a streamlined experience.
Example
$oobeConfig = New-PackageStackOOBEConfiguration -HideOOBE $trueCreates the main metadata object for the provisioning package. This is a required component for any package.
Parameters
-Name <String>: (Required) The display name of the package.-Version <String>: (Required) The package version (e.g., "1.0").-OwnerType <OwnerType>: (Required) The package creator type. Valid options areMicrosoft,SiliconVendor,OEM,SystemIntegrator,MobileOperator,ITAdmin.-Rank <UInt32>: (Required) The installation priority (0-99). A lower value means higher priority.-Id <Guid>: (Optional) A unique ID for the package. If omitted, a new GUID is generated.
Example
$packageConfig = New-PackageStackPackageConfiguration -Name "Base-Config" -Version "1.2" -OwnerType "ITAdmin" -Rank 10Creates a configuration object for a file that needs to be included in the package, used with New-PackageStackProvisioningCommandConfiguration.
Parameters
-Name <String>: (Required) The destination name of the file within the package.-Url <String>: A URL from which to download the file during package creation.-Path <String>: A local file path from which to include the file.-Base64 <String>: A Base64-encoded string representing the file content.
Examples
Reference a file by URL:
$fileConfig = New-PackageStackPackageFileConfiguration -Name "setup.exe" -Url "https://example.com/installers/setup.exe"Reference a file from a local path:
$fileConfig = New-PackageStackPackageFileConfiguration -Name "my-script.ps1" -Path "C:\temp\scripts\my-script.ps1"Creates a configuration for a command-line instruction to be executed during provisioning.
Parameters
-CommandLine <String>: (Required) The command to execute (e.g.,msiexec.exe /i "installer.msi" /quiet).-CommandFile <PackageFile>: (Optional) The primary executable file, created usingNew-PackageStackPackageFileConfiguration.-Dependencies <PackageFile[]>: (Optional) An array of supporting files.-ContinueInstall <Boolean>: (Optional) If set to$true, the provisioning process will continue even if this command fails.-RestartRequired <Boolean>: (Optional) If set to$true, the device will restart after this command completes.-ReturnCodeRestart <Int32>: (Optional) A specific process exit code that should trigger a device restart.-ReturnCodeSuccess <Int32>: (Optional) A specific process exit code that signals success.
Example
$commandFile = New-PackageStackPackageFileConfiguration -Name "Zoom.msi" -Url "https://zoom.us/client/latest/ZoomInstallerFull.msi"
$provCommand = New-PackageStackProvisioningCommandConfiguration -CommandLine 'msiexec.exe /i "Zoom.msi" /quiet' -CommandFile $commandFileCreates a configuration object for a local user account.
Parameters
-Username <String>: (Required) The name for the local user.-Password <String>: (Required) The password for the local user.-Group <UserGroup>: (Optional) The group to add the user to. Options areAdministratorsorStandardUsers. Defaults toStandardUsers.
Example
$userConfig = New-PackageStackUserConfiguration -Username "localadmin" -Password "AComplexPassword!" -Group "Administrators"Creates a configuration object for a Wi-Fi network profile.
Parameters
-SSID <String>: (Required) The SSID (name) of the Wi-Fi network.-SecurityType <SecurityType>: (Required) The network security type. Options areOpen,WEP,WPA2PSK.-SecurityKey <String>: The network password (required forWPA2PSK).-AutoConnect <Boolean>: (Optional) If set to$true, the device will connect automatically. Defaults to$true.-HiddenNetwork <Boolean>: (Optional) Set to$trueif the network does not broadcast its SSID.
Example
$wifiConfig = New-PackageStackWLANSettingConfiguration -SSID "MyCorpWiFi" -SecurityType "WPA2PSK" -SecurityKey "SuperSecretPassword"This is the main cmdlet that assembles all the configuration objects and generates the final .ppkg provisioning package.
Parameters
-PackageConfiguration <PackageConfig>: (Required) The main package configuration object created withNew-PackageStackPackageConfiguration.-AzureConfiguration <Azure>: (Optional) An Azure AD join configuration.-ComputerConfiguration <ComputerAccount>: (Optional) A computer name or AD domain join configuration.-OOBEConfiguration <OOBE>: (Optional) An OOBE customization configuration.-UserConfigurations <User[]>: (Optional) An array of one or more local user configurations.-ProvisioningCommands <ProvisioningCommand[]>: (Optional) An array of one or more provisioning command configurations.-WLANSettings <WLANSetting[]>: (Optional) An array of one or more Wi-Fi configurations.-OutputPath <String>: Saves the generated.ppkgfile to the specified path.-AsBase64: Returns the package content as a Base64-encoded string.-ContainerName <String>: The target container name in Azure Blob Storage. Used to return a SAS URL.-BlobName <String>: The target blob name in Azure Blob Storage. Used to return a SAS URL.
Examples
Create a package and save it as a file:
$packageConfig = New-PackageStackPackageConfiguration -Name "My-First-Package" -Version "1.0" -OwnerType ITAdmin -Rank 0
$user = New-PackageStackUserConfiguration -Username "testuser" -Password "Password123"
New-PackageStackProvisioningPackage -PackageConfiguration $packageConfig -UserConfigurations $user -OutputPath "C:\temp\My-First-Package.ppkg"Create a package and get its Base64 string:
# (Prerequisite: $packageConfig and $user from the previous example)
$base64Package = New-PackageStackProvisioningPackage -PackageConfiguration $packageConfig -UserConfigurations $user -AsBase64
$base64Package.EncodedCreate a package and upload it to Azure for a SAS URL:
# (Prerequisite: $packageConfig and $user from the first example)
# (Prerequisite: $env:AzureWebJobsStorage must be set to your storage connection string)
$sasUrl = New-PackageStackProvisioningPackage -PackageConfiguration $packageConfig -UserConfigurations $user -ContainerName "packages" -BlobName "My-First-Package.ppkg"
$sasUrl.UrlOnce you have generated a .ppkg file, there are several ways to apply it to a Windows device to configure its settings.
For new devices that have not yet gone through the initial Windows setup, this is the most seamless method.
- Copy your
.ppkgfile to the root directory of a USB flash drive. - When the device is on the first screen of the Out-of-Box Experience (OOBE), insert the USB drive.
- Windows will automatically detect the provisioning package and apply its settings. If multiple packages are present, you may be prompted to choose one.
For devices that are already set up and running Windows, you can apply a package directly.
- Transfer the
.ppkgfile to the target device. - Double-click the
.ppkgfile. - A security prompt will appear asking if you trust the source of the package. Click Yes to approve and install the package. The settings will be applied immediately.
On a running system, you can also use PowerShell to install a package, which is useful for scripting and automation.
- Open a PowerShell terminal with administrator privileges.
- Run the
Install-ProvisioningPackagecmdlet, providing the path to your file.
Install-ProvisioningPackage -PackagePath "C:\path\to\your\package.ppkg" -ForceInstallIn a corporate environment, you can deploy provisioning packages to multiple devices at scale using management tools.
- Microsoft Intune: You can upload the
.ppkgfile to Intune and assign it to device groups. Intune will handle the deployment and ensure the settings are applied to all targeted devices. - Microsoft Configuration Manager (SCCM): You can create an application or package in Configuration Manager to distribute and install the
.ppkgfile on managed clients.
Contributions are welcome and greatly appreciated! Whether it's reporting a bug, suggesting a new feature, or submitting code changes, your help is valuable.
If you encounter a bug or have an idea for a new feature, please open an issue on the GitHub repository.
When filing an issue, please provide as much detail as possible:
- For Bugs: Include steps to reproduce the issue, the version you are using, and any relevant error messages.
- For Feature Requests: Clearly describe the new functionality you would like to see and explain why it would be a valuable addition.
If you would like to contribute code, please follow these steps
- Fork the repository on GitHub.
- Create a new branch for your feature or bug fix (e.g.,
feature/add-new-settingorfix/hostname-bug). - Make your changes in your new branch. Please adhere to the existing coding style.
- Write clear commit messages that explain the "what" and "why" of your changes.
- Submit a pull request from your branch to the main branch of the main repository.
- In the pull request description, clearly explain the changes you have made and reference any related issues.
PackageStack is licensed under the MIT License. You are free to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the software. For more details, see the LICENSE.txt file included in the repository.
This project has utilized AI and Large Language Models (LLMs) for the sole purpose of generating and refining the README.md documentation file. The source code for the project itself has been written by human developers.