I have a PowerShell script that uses WPF to create a GUI app.
I cannot make this thing bundle no matter what I try.
Here is the structure:
PackagingTest
-------Scripts
-----------PackTestHello.ps1
-----------PingHostV2.1.ps1
-------icon_18.ico
-------Package.psd1
-------PackageTest.ps1
My subscripts are dot-sourced and there are no spaces or special chars in any filename or folder path:
Add-AdminTab -TabName "Network" -Description "Tools for network management configuration and troubleshooting." -ButtonsConfig @(
@{Label="Test"; ScriptPath=. "$PSScriptRoot\Scripts\PackTestHello.ps1"; Tooltip="Test script"}
When I execute the “package script as exe” command in VSCode, I can get the script to package successfully, but only with this very simple test script which contains only one line of code:
[System.Windows.MessageBox]::Show("This is a test!", "Testing 123", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
However, when I launch the generated exe file, the subscript fires first, before the main script loads. After clicking OK button, the main script fires. But when I attempt to click the button to launch the test subscript I get an error window:
Error:
Script not found: OK
(I assume the subscript firing immediately is due to the dot-sourcing of the subscripts. But if I don’t dot-source the subscripts, they do not get included in the package.)
If I attempt to package the script using Packager.exe, I get the same result.
Now, that was just a test to see if I could get anything to package, hence the very simple test script.
If I attempt to package the script with my actual subscripts included (Any real subscript), the packaging fails.
Using Packager.exe, the process hangs at “Generating assembly” until the window is closed. Nothing is produced.
When I execute the “package script as exe” command in VSCode, I get the following output in Terminal, nothing is produced:
PS D:\Projects\PackagingTest> Merge-Script -ConfigFile 'd:\Projects\PackagingTest\package.psd1' -Verbose
VERBOSE: OutputPath is D:\Projects\PackagingTest
VERBOSE: Bundling D:\Projects\PackagingTest\PackageTest.ps1
VERBOSE: Parsing file D:\Projects\PackagingTest\PackageTest.ps1.
VERBOSE: Parsing file D:\Projects\PackagingTest\scripts\network\pinghostv2.1.ps1.
VERBOSE: Packaging C:\Users\lowryb\AppData\Local\Temp\PackageTest.ps1
VERBOSE: Creating temp directory: D:\Projects\PackagingTest\bin\a61b0093c2fc4d8f868a19edfbe3500f
VERBOSE: Packaging modules...
VERBOSE: Checking dotnet version.
VERBOSE: Checking dotnet SDK version.
VERBOSE: 9.0.101
VERBOSE: .NET SDK Version: 9.0.101
VERBOSE: Creating package project.
VERBOSE: Using .NET Framework version: net462
Merge-Script: At line:260 char:6
+ while (!$exit) {
+ ~
Missing '=' operator after key in hash literal.
Merge-Script: OutputPath is D:\Projects\PackagingTest
Bundling D:\Projects\PackagingTest\PackageTest.ps1
Parsing file D:\Projects\PackagingTest\PackageTest.ps1.
Parsing file D:\Projects\PackagingTest\scripts\network\pinghostv2.1.ps1.
Packaging C:\Users\lowryb\AppData\Local\Temp\PackageTest.ps1
Creating temp directory: D:\Projects\PackagingTest\bin\a61b0093c2fc4d8f868a19edfbe3500f
Packaging modules...
Checking dotnet version.
Checking dotnet SDK version.
9.0.101
.NET SDK Version: 9.0.101
Creating package project.
Using .NET Framework version: net462
I get the same type of response no matter what Actual (not test) subscript I include in the main script.
Here is my main script:
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName PresentationCore
Add-Type -AssemblyName WindowsBase
# Create the main window
$window = New-Object System.Windows.Window
$window.Title = "KC Admin Console"
$window.Height = 524
$window.Width = 725
$window.WindowStartupLocation = "CenterScreen"
# Create main grid for the window with two rows
$mainGrid = New-Object System.Windows.Controls.Grid
$window.Content = $mainGrid
# Define rows for header and tab control
$mainGrid.RowDefinitions.Add((New-Object System.Windows.Controls.RowDefinition -Property @{Height = "Auto"}))
$mainGrid.RowDefinitions.Add((New-Object System.Windows.Controls.RowDefinition -Property @{Height = "*"}))
# Create header label - FIRST create the object
$headerLabel = New-Object System.Windows.Controls.Label
$headerLabel.Content = "Kern County ITS Administrators Console"
$headerLabel.FontSize = 25
$headerLabel.FontWeight = "Bold"
$headerLabel.Foreground = "#9e3106"
$headerLabel.HorizontalAlignment = "Center"
$headerLabel.Margin = New-Object System.Windows.Thickness(0, 10, 0, 5)
# THEN set up visual effects
$dropShadow = New-Object System.Windows.Media.Effects.DropShadowEffect
$dropShadow.Color = "Gray"
$dropShadow.Direction = 315
$dropShadow.ShadowDepth = 5
$dropShadow.Opacity = 0.7
$dropShadow.BlurRadius = 5
$headerLabel.Effect = $dropShadow
# Finally add to the grid
$mainGrid.Children.Add($headerLabel)
[System.Windows.Controls.Grid]::SetRow($headerLabel, 0)
# Create TabControl (now in second row)
$tabControl = New-Object System.Windows.Controls.TabControl
$tabControl.Margin = New-Object System.Windows.Thickness(10)
$mainGrid.Children.Add($tabControl)
[System.Windows.Controls.Grid]::SetRow($tabControl, 1)
# Function to create a new tab with the required layout
function Add-AdminTab
{
param (
[string]$TabName,
[string]$Description,
[hashtable[]]$ButtonsConfig # Array of hashtables with 'Label' 'ScriptPath' and optional 'Tooltip' keys
)
# Create new tab
$tabItem = New-Object System.Windows.Controls.TabItem
$tabItem.Header = $TabName
$tabControl.Items.Add($tabItem)
# Create grid for tab content
$grid = New-Object System.Windows.Controls.Grid
$tabItem.Content = $grid
# Define rows for the grid
$grid.RowDefinitions.Add((New-Object System.Windows.Controls.RowDefinition -Property @{Height = "Auto"}))
$grid.RowDefinitions.Add((New-Object System.Windows.Controls.RowDefinition -Property @{Height = "Auto"}))
$grid.RowDefinitions.Add((New-Object System.Windows.Controls.RowDefinition -Property @{Height = "*"}))
# Header label
$headerLabel = New-Object System.Windows.Controls.Label
$headerLabel.Content = $TabName
$headerLabel.FontSize = 18
$headerLabel.FontWeight = "Bold"
$headerLabel.HorizontalAlignment = "Center"
$headerLabel.Margin = New-Object System.Windows.Thickness(0, 10, 0, 10)
$grid.Children.Add($headerLabel)
[System.Windows.Controls.Grid]::SetRow($headerLabel, 0)
# Description TextBox
$descriptionTextBox = New-Object System.Windows.Controls.TextBox
$descriptionTextBox.Text = $Description
$descriptionTextBox.TextWrapping = "Wrap"
$descriptionTextBox.HorizontalAlignment = "Center"
$descriptionTextBox.IsReadOnly = $true
$descriptionTextBox.Margin = New-Object System.Windows.Thickness(10, 5, 10, 15)
$descriptionTextBox.Padding = New-Object System.Windows.Thickness(5)
$descriptionTextBox.BorderThickness = New-Object System.Windows.Thickness(0)
$descriptionTextBox.Height = 30
$grid.Children.Add($descriptionTextBox)
[System.Windows.Controls.Grid]::SetRow($descriptionTextBox, 1)
# Create grid for buttons (3 columns)
$buttonsGrid = New-Object System.Windows.Controls.Grid
$buttonsGrid.Margin = New-Object System.Windows.Thickness(10)
$grid.Children.Add($buttonsGrid)
[System.Windows.Controls.Grid]::SetRow($buttonsGrid, 2)
# Define columns for the buttons grid
$buttonsGrid.ColumnDefinitions.Add((New-Object System.Windows.Controls.ColumnDefinition))
$buttonsGrid.ColumnDefinitions.Add((New-Object System.Windows.Controls.ColumnDefinition))
$buttonsGrid.ColumnDefinitions.Add((New-Object System.Windows.Controls.ColumnDefinition))
# Set up number of rows based on the number of buttons
$buttonsPerColumn = [Math]::Ceiling($ButtonsConfig.Count / 3)
for ($i = 0; $i -lt $buttonsPerColumn; $i++)
{
$buttonsGrid.RowDefinitions.Add((New-Object System.Windows.Controls.RowDefinition -Property @{Height = "Auto"}))
}
# Add buttons
for ($i = 0; $i -lt $ButtonsConfig.Count; $i++)
{
$column = [Math]::Floor($i / $buttonsPerColumn)
$row = $i % $buttonsPerColumn
$button = New-Object System.Windows.Controls.Button
$button.Content = $ButtonsConfig[$i].Label
$button.Margin = New-Object System.Windows.Thickness(10, 5, 10, 5)
$button.Padding = New-Object System.Windows.Thickness(5)
$button.Height = 40
# Store script path and PS7 requirement in Tag property as a hashtable
$buttonInfo = @{
ScriptPath = $ButtonsConfig[$i].ScriptPath
RequiresPS7 = $ButtonsConfig[$i].RequiresPS7 -eq $true
}
$button.Tag = $buttonInfo
# Add tooltip
if ($ButtonsConfig[$i].Tooltip)
{
$button.ToolTip = $ButtonsConfig[$i].Tooltip
} else
{
$button.ToolTip = "Run: $($ButtonsConfig[$i].ScriptPath)"
}
# Add click event handler with error handling
$button.Add_Click({
param($sender, $e)
# Get button info directly
$buttonInfo = $sender.Tag
$scriptPath = $buttonInfo.ScriptPath
$requiresPS7 = $buttonInfo.RequiresPS7
try
{
if (Test-Path $scriptPath)
{
if ($requiresPS7)
{
Start-Process pwsh -ArgumentList "-NoExit -File `"$scriptPath`""
} else
{
Start-Process powershell -ArgumentList "-NoExit -File `"$scriptPath`""
}
} else
{
[System.Windows.MessageBox]::Show("Script not found: $scriptPath", "Error", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Error)
}
} catch
{
[System.Windows.MessageBox]::Show("An error occurred while running script: $scriptPath`n$_", "Error", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Error)
}
})
$buttonsGrid.Children.Add($button)
[System.Windows.Controls.Grid]::SetRow($button, $row)
[System.Windows.Controls.Grid]::SetColumn($button, $column)
}
}
# Set Tab Contents
Add-AdminTab -TabName "Network" -Description "Tools for network management configuration and troubleshooting." -ButtonsConfig @(
@{Label="Connectivity Test"; ScriptPath=. "$PSScriptRoot\Scripts\PingHostV2.1.ps1"; Tooltip="Test connectivity to a host using Ping"}
)
Add-AdminTab -TabName "Launchers" -Description "Tools for connecting to services and utilities." -ButtonsConfig @(
)
# Show the Window
$window.ShowDialog() | Out-Null
Here is the only subscript included:
# Function to ping a host
function Ping-Host {
param (
[string]$Computer,
[switch]$Continuous
)
$formatTable = @{
Label = "Status"
Expression = {
if ($_.Status -eq "Success") {
$color = "Green"
} else {
$color = "Red"
}
$status = $_.Status.ToString()
$Host.UI.RawUI.ForegroundColor = $color
Write-Output $status
$Host.UI.RawUI.ForegroundColor = "White"
}
}
try {
if ($Continuous) {
Write-Host "Press Ctrl+C to stop pinging..." -ForegroundColor Yellow
while ($true) {
$result = Test-Connection -ComputerName $Computer -Count 1 -ErrorAction Stop
if ($result) {
Write-Host "Reply from $Computer : time=$($result.ResponseTime)ms" -ForegroundColor Green
}
Start-Sleep -Seconds 1
}
} else {
$result = Test-Connection -ComputerName $Computer -Count 4 -ErrorAction Stop
foreach ($ping in $result) {
Write-Host "Reply from $Computer : time=$($ping.ResponseTime)ms" -ForegroundColor Green
}
}
}
catch {
Write-Host "Unable to ping $Computer : $($_.Exception.Message)" -ForegroundColor Red
}
}
#Set Environment Values
[console]::windowwidth=50;
[console]::windowheight=70;
[console]::bufferwidth=[console]::windowwidth;
$host.UI.RawUI.WindowTitle="PING";
# Start a loop that will run until the user selects the "Exit" option
$exit = $false
while (!$exit) {
Clear-Host
# Header
# Banner text definitions
$BANNER1 = @"
__________.___ _______ ________
\______ \ |\ \ / _____/
| ___/ |/ | \/ \ ___
| | | / | \ \_\ \
|____| |___\____|__ /\______ /
\/ \/
"@
# Display centered banners
Clear-Host
Write-Host ""
Write-Host ""
Write-Host ""
$banner1Lines = $BANNER1.TrimEnd() -split "`n"
$maxWidth = ($banner1Lines | Measure-Object -Property Length -Maximum).Maximum
$hostWidth = $Host.UI.RawUI.WindowSize.Width
foreach ($line in $banner1Lines) {
$line = $line.TrimEnd()
$paddingLength = [Math]::Max(0, [math]::Floor(($hostWidth - $line.Length) / 2))
$padding = if ($paddingLength -gt 0) { " " * $paddingLength } else { "" }
Write-Host ($padding + $line) -ForegroundColor Magenta
}
Write-Host ""
Write-Host ""
Write-Host ("*" * $hostWidth) -ForegroundColor Red
#Command
$computer = Read-Host -Prompt " Domain Name or IP address"
# Ask user for ping type
Write-Host ""
Write-Host " Ping Type:"
Write-Host " 1) Four Pings" -ForegroundColor Green
Write-Host " 2) Continuous Ping" -ForegroundColor Green
Write-Host ""
$pingType = Read-Host -Prompt " Enter a selection (1 or 2):"
if ($pingType -eq "1") {
Ping-Host -Computer $computer
} elseif ($pingType -eq "2") {
Ping-Host -Computer $computer -Continuous
} else {
Write-Host " Invalid Selection. Defaulting to Four Pings." -ForegroundColor Red
Ping-Host -Computer $computer
}
# Ask user what to do next
while ($true) {
Write-Host ""
Write-Host " What Now?"
Write-Host " 1) Ping Again" -ForegroundColor Green
Write-Host " 2) Ping new Host" -ForegroundColor Green
Write-Host " 3) Exit" -ForegroundColor Green
Write-Host ""
$doNext = Read-Host -Prompt "Enter a selection (1, 2, or 3):"
switch ($doNext) {
"1" {
# Repeat the same ping with same settings
if ($pingType -eq "1") {
Ping-Host -Computer $computer
} elseif ($pingType -eq "2") {
Ping-Host -Computer $computer -Continuous
}
}
"2" {
break # Exit the inner loop to prompt for a new host
}
"3" {
$exit = $true
break # Exit the inner loop and the outer loop
}
default {
Write-Host " Invalid Selection" -ForegroundColor Red
}
}
if ($doNext -eq "2" -or $doNext -eq "3") {
break
}
}
}
Here is my package.psd1 file:
@{
Root = 'D:\Projects\PackagingTest\PackageTest.ps1'
OutputPath = 'D:\Projects\PackagingTest'
Package = @{
ApplicationIconPath = 'D:\Projects\PackagingTest\icon_18.ico'
Copyright = 'B.Lowry'
DisableQuickEdit = $true
DotNetVersion = 'v4.6.2'
Enabled = $true
FileDescription = 'Collection of PowerShell tools for system administration'
FileVersion = '1.2.1'
HideConsoleWindow = $true
Host = 'Default'
PowerShellVersion = 'Windows PowerShell'
ProductName = 'Kern County ITS Administrators Console'
ProductVersion = '1.2.1'
RequireElevation = $true
RuntimeIdentifier = 'win-x64'
}
Bundle = @{
Enabled = $true
Modules = $true
}
}
For the failed attempts: a csproj file is created in the bin subdir which contains:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<FileVersion>1.2.1</FileVersion>
<Company />
<Copyright>B.Lowry</Copyright>
<AssemblyTitle>Collection of PowerShell tools for system administration</AssemblyTitle>
<Product>Kern County ITS Administrators Console</Product>
<InformationalVersion>1.2.1</InformationalVersion>
<ApplicationIcon>D:\Projects\PackagingTest\icon_18.ico</ApplicationIcon>
<TargetFramework>net462</TargetFramework>
<Prefer32Bit>true</Prefer32Bit>
<UseWPF>true</UseWPF>
</PropertyGroup>
<PropertyGroup />
<ItemGroup>
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.PowerShell.ConsoleHost">
<HintPath>C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.PowerShell.ConsoleHost\v4.0_3.0.0.0__31bf3856ad364e35\Microsoft.PowerShell.ConsoleHost.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Reference Include="System.Management.Automation">
<HintPath>C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll</HintPath>
</Reference>
</ItemGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="script.ps1" />
</ItemGroup>
<ItemGroup />
</Project>
VSCode:
Version: 1.99.0 (user setup)
Commit: 4437686ffebaf200fa4a6e6e67f735f3edf24ada
Date: 2025-04-02T21:35:19.530Z
Electron: 34.3.2
ElectronBuildId: 11161073
Chromium: 132.0.6834.210
Node.js: 20.18.3
V8: 13.2.152.41-electron.0
OS: Windows_NT x64 10.0.22631
Powershell Protools:
Identifier
ironmansoftware.powershellprotools
Version
2024.12.0
Last Updated
2025-04-04, 19:32:31
Powershell plugin:
Identifier
ms-vscode.powershell
Version
2025.0.0
Last Updated
2025-01-21, 16:05:18
Please help me understand what I am doing wrong here.
Any help you can offer would be appreciated.
Cheers,
Brian