diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index a7967fc..1ea4c69 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -24,6 +24,7 @@ jobs: shell: bash run: | set -euo pipefail + version="0.1.1" dotnet publish src/MrTrustLauncher.csproj \ --configuration Release \ --runtime win-x64 \ @@ -37,12 +38,13 @@ jobs: shell: bash run: | set -euo pipefail - version="0.1.0" + version="0.1.1" package_root="dist/MrTrust-${version}" rm -rf "$package_root" "dist/MrTrust-${version}.zip" mkdir -p "$package_root/scripts" "$package_root/assets/certificates" "$package_root/docs" cp dist/MrTrust.exe "$package_root/" cp MrTrust.ps1 README.md "$package_root/" + cp assets/MrTrust.ico "$package_root/assets/" cp scripts/Install-MrTrust.ps1 scripts/Uninstall-MrTrust.ps1 scripts/Start-MrTrustGui.ps1 "$package_root/scripts/" cp assets/certificates/MrSphay-LocalTrust-Root.cer "$package_root/assets/certificates/" cp assets/certificates/MrSphay-CodeSigning.cer "$package_root/assets/certificates/" @@ -58,5 +60,5 @@ jobs: - name: Upload release artifact uses: actions/upload-artifact@v3 with: - name: MrTrust-0.1.0 - path: dist/MrTrust-0.1.0.zip + name: MrTrust-0.1.1 + path: dist/MrTrust-0.1.1.zip diff --git a/CHANGELOG.md b/CHANGELOG.md index f6805f4..3305846 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.1.1 + +- Added a custom MrTrust application icon and embedded it into the launcher. +- Improved the GUI layout so status text is not clipped. +- Updated the Gitea artifact version to `0.1.1`. + ## 0.1.0 - Added MrTrust certificate generation, installation, removal, and signing scripts. diff --git a/README.md b/README.md index 600a10c..321b351 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Remove the trust certificate: Build a user-facing ZIP release: ```powershell -.\scripts\New-MrTrustRelease.ps1 -Version 0.1.0 +.\scripts\New-MrTrustRelease.ps1 -Version 0.1.1 ``` The Gitea workflow `.gitea/workflows/build.yml` builds the Windows launcher EXE on an `ubuntu-latest` runner with .NET Windows cross-targeting, then uploads the ZIP as an artifact. diff --git a/assets/MrTrust.ico b/assets/MrTrust.ico new file mode 100644 index 0000000..7b5aef1 Binary files /dev/null and b/assets/MrTrust.ico differ diff --git a/scripts/Build-MrTrustExe.ps1 b/scripts/Build-MrTrustExe.ps1 index 2d4190a..d479496 100644 --- a/scripts/Build-MrTrustExe.ps1 +++ b/scripts/Build-MrTrustExe.ps1 @@ -13,6 +13,7 @@ function Resolve-FullPath { $root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) $sourcePath = Join-Path $root "src\MrTrustLauncher.cs" +$iconPath = Join-Path $root "assets\MrTrust.ico" $resolvedOutputPath = Resolve-FullPath $OutputPath $outputDirectory = Split-Path -Parent $resolvedOutputPath @@ -20,6 +21,10 @@ if (-not (Test-Path -LiteralPath $sourcePath)) { throw "Launcher source not found: $sourcePath" } +if (-not (Test-Path -LiteralPath $iconPath)) { + & (Join-Path $root "scripts\New-MrTrustIcon.ps1") -OutputPath $iconPath +} + New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null $compilerCandidates = @( @@ -38,6 +43,7 @@ if (-not $compiler) { /optimize+ ` /platform:anycpu ` /out:$resolvedOutputPath ` + /win32icon:$iconPath ` /reference:System.Windows.Forms.dll ` /reference:System.Drawing.dll ` $sourcePath diff --git a/scripts/New-MrTrustIcon.ps1 b/scripts/New-MrTrustIcon.ps1 new file mode 100644 index 0000000..7ae79c7 --- /dev/null +++ b/scripts/New-MrTrustIcon.ps1 @@ -0,0 +1,61 @@ +[CmdletBinding()] +param( + [string]$OutputPath = ".\assets\MrTrust.ico" +) + +$ErrorActionPreference = "Stop" + +function Resolve-FullPath { + param([Parameter(Mandatory)][string]$Path) + + $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) +} + +Add-Type -AssemblyName System.Drawing + +$resolvedOutputPath = Resolve-FullPath $OutputPath +$outputDirectory = Split-Path -Parent $resolvedOutputPath +New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null + +$bitmap = [Drawing.Bitmap]::new(64, 64) +$graphics = [Drawing.Graphics]::FromImage($bitmap) +$graphics.SmoothingMode = [Drawing.Drawing2D.SmoothingMode]::AntiAlias +$graphics.Clear([Drawing.Color]::Transparent) + +$backgroundBrush = [Drawing.SolidBrush]::new([Drawing.Color]::FromArgb(27, 32, 35)) +$accentBrush = [Drawing.SolidBrush]::new([Drawing.Color]::FromArgb(28, 185, 111)) +$lightBrush = [Drawing.SolidBrush]::new([Drawing.Color]::FromArgb(225, 231, 227)) +$shadowBrush = [Drawing.SolidBrush]::new([Drawing.Color]::FromArgb(70, 0, 0, 0)) +$outlinePen = [Drawing.Pen]::new([Drawing.Color]::FromArgb(71, 84, 90), 3) + +$graphics.FillRectangle($shadowBrush, 9, 10, 48, 48) +$graphics.FillRectangle($backgroundBrush, 7, 7, 48, 48) +$graphics.DrawRectangle($outlinePen, 7, 7, 48, 48) +$graphics.FillRectangle($accentBrush, 13, 13, 12, 36) +$graphics.FillRectangle($accentBrush, 13, 13, 30, 12) +$graphics.FillRectangle($lightBrush, 31, 29, 14, 20) + +$font = [Drawing.Font]::new("Segoe UI", 18, [Drawing.FontStyle]::Bold, [Drawing.GraphicsUnit]::Pixel) +$graphics.DrawString("T", $font, $lightBrush, 32, 27) + +$handle = $bitmap.GetHicon() +$icon = [Drawing.Icon]::FromHandle($handle) +$stream = [IO.File]::Open($resolvedOutputPath, [IO.FileMode]::Create) +try { + $icon.Save($stream) +} +finally { + $stream.Dispose() + $icon.Dispose() + $font.Dispose() + $outlinePen.Dispose() + $shadowBrush.Dispose() + $lightBrush.Dispose() + $accentBrush.Dispose() + $backgroundBrush.Dispose() + $graphics.Dispose() + $bitmap.Dispose() +} + +Write-Host "Created icon:" +Write-Host " $resolvedOutputPath" diff --git a/scripts/New-MrTrustRelease.ps1 b/scripts/New-MrTrustRelease.ps1 index 6028905..6ee3a3a 100644 --- a/scripts/New-MrTrustRelease.ps1 +++ b/scripts/New-MrTrustRelease.ps1 @@ -20,6 +20,7 @@ $output = Resolve-FullPath $OutputDirectory $packageRoot = Join-Path $output "MrTrust-$Version" $zipPath = Join-Path $output "MrTrust-$Version.zip" $exePath = Join-Path $output "MrTrust.exe" +$iconPath = Join-Path $root "assets\MrTrust.ico" if (Test-Path -LiteralPath $packageRoot) { Remove-Item -LiteralPath $packageRoot -Recurse -Force @@ -30,6 +31,10 @@ New-Item -ItemType Directory -Force -Path (Join-Path $packageRoot "scripts") | O New-Item -ItemType Directory -Force -Path (Join-Path $packageRoot "assets\certificates") | Out-Null New-Item -ItemType Directory -Force -Path (Join-Path $packageRoot "docs") | Out-Null +if (-not (Test-Path -LiteralPath $iconPath)) { + & (Join-Path $root "scripts\New-MrTrustIcon.ps1") -OutputPath $iconPath +} + & (Join-Path $root "scripts\Build-MrTrustExe.ps1") -OutputPath $exePath if ($SigningThumbprint) { @@ -52,6 +57,7 @@ if ($SigningThumbprint) { Copy-Item -LiteralPath $exePath -Destination $packageRoot Copy-Item -LiteralPath (Join-Path $root "MrTrust.ps1") -Destination $packageRoot Copy-Item -LiteralPath (Join-Path $root "README.md") -Destination $packageRoot +Copy-Item -LiteralPath $iconPath -Destination (Join-Path $packageRoot "assets") Copy-Item -LiteralPath (Join-Path $root "scripts\Install-MrTrust.ps1") -Destination (Join-Path $packageRoot "scripts") Copy-Item -LiteralPath (Join-Path $root "scripts\Uninstall-MrTrust.ps1") -Destination (Join-Path $packageRoot "scripts") Copy-Item -LiteralPath (Join-Path $root "scripts\Start-MrTrustGui.ps1") -Destination (Join-Path $packageRoot "scripts") diff --git a/scripts/Start-MrTrustGui.ps1 b/scripts/Start-MrTrustGui.ps1 index 520455c..8416682 100644 --- a/scripts/Start-MrTrustGui.ps1 +++ b/scripts/Start-MrTrustGui.ps1 @@ -9,6 +9,7 @@ Add-Type -AssemblyName System.Drawing $script:RootPath = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) $script:RootCertificatePath = Join-Path $script:RootPath "assets\certificates\MrSphay-LocalTrust-Root.cer" $script:PublisherCertificatePath = Join-Path $script:RootPath "assets\certificates\MrSphay-CodeSigning.cer" +$script:IconPath = Join-Path $script:RootPath "assets\MrTrust.ico" function Test-IsAdministrator { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() @@ -75,11 +76,11 @@ function Refresh-MrTrustStatus { $script:ExpiryLabel.Text = $rootCertificate.NotAfter.ToString("yyyy-MM-dd") if ($rootInstalled -and $publisherInstalled) { - Set-StatusText "Trusted for $scope" + Set-StatusText "Trusted" $script:StatusPill.BackColor = [Drawing.Color]::FromArgb(28, 185, 111) } else { - Set-StatusText "Not installed for $scope" + Set-StatusText "Not installed" $script:StatusPill.BackColor = [Drawing.Color]::FromArgb(242, 153, 74) } } @@ -166,14 +167,17 @@ function Remove-MrTrustCertificates { $form = [Windows.Forms.Form]::new() $form.Text = "MrTrust" $form.StartPosition = "CenterScreen" -$form.ClientSize = [Drawing.Size]::new(760, 520) -$form.MinimumSize = [Drawing.Size]::new(720, 500) +$form.ClientSize = [Drawing.Size]::new(900, 560) +$form.MinimumSize = [Drawing.Size]::new(860, 540) $form.BackColor = [Drawing.Color]::FromArgb(22, 26, 29) $form.Font = [Drawing.Font]::new("Segoe UI", 10) +if (Test-Path -LiteralPath $script:IconPath) { + $form.Icon = [Drawing.Icon]::new($script:IconPath) +} $header = [Windows.Forms.Panel]::new() $header.Dock = "Top" -$header.Height = 108 +$header.Height = 124 $header.BackColor = [Drawing.Color]::FromArgb(27, 32, 35) $form.Controls.Add($header) @@ -183,32 +187,50 @@ $accent.Width = 8 $accent.BackColor = [Drawing.Color]::FromArgb(28, 185, 111) $header.Controls.Add($accent) +$logoBox = [Windows.Forms.PictureBox]::new() +$logoBox.Size = [Drawing.Size]::new(44, 44) +$logoBox.Location = [Drawing.Point]::new(34, 30) +$logoBox.SizeMode = "StretchImage" +if (Test-Path -LiteralPath $script:IconPath) { + $logoBox.Image = [Drawing.Icon]::new($script:IconPath).ToBitmap() +} +$header.Controls.Add($logoBox) + $title = [Windows.Forms.Label]::new() $title.Text = "MrTrust" $title.ForeColor = [Drawing.Color]::White $title.Font = [Drawing.Font]::new("Segoe UI", 24, [Drawing.FontStyle]::Bold) $title.AutoSize = $true -$title.Location = [Drawing.Point]::new(30, 18) +$title.Location = [Drawing.Point]::new(92, 24) $header.Controls.Add($title) $subtitle = [Windows.Forms.Label]::new() $subtitle.Text = "Trust setup for MrSphay signed Windows apps" $subtitle.ForeColor = [Drawing.Color]::FromArgb(177, 190, 183) $subtitle.AutoSize = $true -$subtitle.Location = [Drawing.Point]::new(34, 66) +$subtitle.Location = [Drawing.Point]::new(96, 74) $header.Controls.Add($subtitle) +$statusText = [Windows.Forms.Label]::new() +$statusText.Text = "Status" +$statusText.ForeColor = [Drawing.Color]::FromArgb(177, 190, 183) +$statusText.AutoSize = $true +$statusText.Location = [Drawing.Point]::new(646, 32) +$header.Controls.Add($statusText) + $script:StatusPill = [Windows.Forms.Panel]::new() -$script:StatusPill.Size = [Drawing.Size]::new(14, 14) -$script:StatusPill.Location = [Drawing.Point]::new(610, 42) +$script:StatusPill.Size = [Drawing.Size]::new(16, 16) +$script:StatusPill.Location = [Drawing.Point]::new(646, 62) $script:StatusPill.BackColor = [Drawing.Color]::FromArgb(242, 153, 74) $header.Controls.Add($script:StatusPill) $script:StatusLabel = [Windows.Forms.Label]::new() $script:StatusLabel.Text = "Checking..." $script:StatusLabel.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227) -$script:StatusLabel.AutoSize = $true -$script:StatusLabel.Location = [Drawing.Point]::new(632, 38) +$script:StatusLabel.AutoSize = $false +$script:StatusLabel.AutoEllipsis = $true +$script:StatusLabel.Location = [Drawing.Point]::new(674, 57) +$script:StatusLabel.Size = [Drawing.Size]::new(190, 28) $header.Controls.Add($script:StatusLabel) $content = [Windows.Forms.Panel]::new() @@ -219,8 +241,8 @@ $form.Controls.Add($content) $infoPanel = [Windows.Forms.Panel]::new() $infoPanel.BackColor = [Drawing.Color]::FromArgb(31, 37, 40) -$infoPanel.Size = [Drawing.Size]::new(700, 210) -$infoPanel.Location = [Drawing.Point]::new(30, 34) +$infoPanel.Size = [Drawing.Size]::new(820, 226) +$infoPanel.Location = [Drawing.Point]::new(40, 34) $content.Controls.Add($infoPanel) $scopeLabel = [Windows.Forms.Label]::new() @@ -289,7 +311,7 @@ $installButton.BackColor = [Drawing.Color]::FromArgb(28, 185, 111) $installButton.ForeColor = [Drawing.Color]::White $installButton.FlatStyle = "Flat" $installButton.Size = [Drawing.Size]::new(180, 46) -$installButton.Location = [Drawing.Point]::new(30, 274) +$installButton.Location = [Drawing.Point]::new(40, 292) $installButton.Add_Click({ Install-MrTrustCertificates }) $content.Controls.Add($installButton) @@ -299,7 +321,7 @@ $removeButton.BackColor = [Drawing.Color]::FromArgb(44, 52, 56) $removeButton.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227) $removeButton.FlatStyle = "Flat" $removeButton.Size = [Drawing.Size]::new(180, 46) -$removeButton.Location = [Drawing.Point]::new(230, 274) +$removeButton.Location = [Drawing.Point]::new(240, 292) $removeButton.Add_Click({ Remove-MrTrustCertificates }) $content.Controls.Add($removeButton) @@ -309,15 +331,15 @@ $refreshButton.BackColor = [Drawing.Color]::FromArgb(44, 52, 56) $refreshButton.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227) $refreshButton.FlatStyle = "Flat" $refreshButton.Size = [Drawing.Size]::new(140, 46) -$refreshButton.Location = [Drawing.Point]::new(430, 274) +$refreshButton.Location = [Drawing.Point]::new(440, 292) $refreshButton.Add_Click({ Refresh-MrTrustStatus }) $content.Controls.Add($refreshButton) $note = [Windows.Forms.Label]::new() $note.Text = "MrTrust installs public certificates only. It does not disable Defender, SmartScreen, UAC, or enterprise policies." $note.ForeColor = [Drawing.Color]::FromArgb(177, 190, 183) -$note.Location = [Drawing.Point]::new(30, 352) -$note.Size = [Drawing.Size]::new(700, 48) +$note.Location = [Drawing.Point]::new(40, 376) +$note.Size = [Drawing.Size]::new(820, 48) $content.Controls.Add($note) $form.Add_Shown({ Refresh-MrTrustStatus }) diff --git a/src/MrTrustLauncher.csproj b/src/MrTrustLauncher.csproj index b80d92f..14e922f 100644 --- a/src/MrTrustLauncher.csproj +++ b/src/MrTrustLauncher.csproj @@ -10,5 +10,6 @@ enable true false + ..\assets\MrTrust.ico