Skip to content

BED-6446: Add winres tool to generate Windows Resources#169

Open
StranDutton wants to merge 3 commits intomainfrom
BED-6446-include-version-in-exe-props
Open

BED-6446: Add winres tool to generate Windows Resources#169
StranDutton wants to merge 3 commits intomainfrom
BED-6446-include-version-in-exe-props

Conversation

@StranDutton
Copy link
Contributor

@StranDutton StranDutton commented Feb 23, 2026

Ticket

https://specterops.atlassian.net/browse/BED-6446

Summary

  • Added go-winres in order to apply manifest information to the .exe for Windows users.
  • Added new step in the build and publish GitHub actions in order to generate these resources just before the application is built and published.
  • Unit tests added for the script that generates the file that gets read by go-winres.
  • Use of the winres tool required a version bump from Go 1.23.0 -> 1.24.0 - I reran unit tests and performed a smoke test to make sure everything still works as expected after updating.

Testing

Testing Instructions

  • Goal: run the script I created to generate the Windows resources, build the image locally, and then check the .exe to make sure that file properties get populated.

    1. Install go-winres onto the windows machine (not in the AzH project directory)

      • go install http://github.com/tc-hib/go-winres@latest
    2. After go-winres is installed, pull my branch BED-6446-include-version-in-exe-props

    3. Then run go run cmd/generate-windows-resources/generate-windows-resources.go "v0.0.0-rolling+8675"

    • Ensure the .syso file is generated
    1. Build AzureHound (on Windows) with this command:

      $version = git describe --tags --exact-match 2>$null
      if (-not $version) { $version = git rev-parse HEAD }
      go build -ldflags "-s -w -X github.com/bloodhoundad/azurehound/v2/constants.Version=$version" .
    2. Find the .exe and inspect the properties to ensure everything looks correct.

    • Expected values:
    "CompanyName": "Created by the BloodHound Enterprise team - https://bloodhoundenterprise.io",
    "FileDescription": "The official tool for collecting Azure data for BloodHound and BloodHound Enterprise",
    "FileVersion": "0.0.0.0",
    "LegalCopyright": "Copyright (C) 2026 Specter Ops, Inc.",
    "OriginalFilename": "azurehound.exe",
    "ProductName": "AzureHound",
    "ProductVersion": "v0.0.0-rolling+8675"
    • Also, it should have a Hound icon included 🤞

Demo

All information populated as expected when examining the properties of the .exe ✅
image
image

//Used `act` to run GitHub Actions locally - output for Windows matrix:
[...]
| ✓ Windows resources generated successfully!
|   Product Version: v2.9.0
[Publish/build                  ]   ✅  Success - Main Generate Windows Resources [2.378763709s]
[Publish/build                  ] ⭐ Run Main Build
[Publish/build                  ]   🐳  docker exec cmd=[bash -e /var/run/act/workflow/3] user= workdir=
| go: downloading github.com/judwhite/go-svc v1.2.1
| go: downloading github.com/inconshreveable/mousetrap v1.1.0
| ✓ Windows resources generated successfully!
|   Product Version: v0.0.0-rolling+5f8807a4107f0b80debaf79b2d255bfa7078a54a
[Build/build                    ]   ✅  Success - Main Generate Windows Resources [2.705380417s]
[...]

Summary by CodeRabbit

  • New Features

    • Windows builds now embed application icon and version metadata into executables.
  • Chores

    • Build and publish workflows updated to run Windows resource generation on Windows runners (allowed to continue on failure).
    • Tooling and dependency updates to support Windows resource generation.

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

Walkthrough

Adds a Windows resource generation utility and invokes it from Build and Publish GitHub workflow jobs on the Windows matrix, plus supporting tests, a new Company constant, and dependency/tooling updates in go.mod.

Changes

Cohort / File(s) Summary
Workflows
.github/workflows/build.yml, .github/workflows/publish.yml
Introduce a Generate Windows Resources step that runs only when matrix.os == 'windows', marked continue-on-error: true, executing the new CLI with a version argument before the build/publish compilation steps.
Windows resource CLI
cmd/generate-windows-resources/generate-windows-resources.go
New command that parses a product version, builds a winres.json with icon and version metadata (localized strings, fixed file/product version fields), writes winres/winres.json, and runs go tool go-winres make. Includes error handling and success output.
Tests for CLI
cmd/generate-windows-resources/generate-windows-resources_test.go
Unit tests for version parsing and winres.json construction validating icon mapping, version fields, localized info, copyright year, and original filename.
Constants
constants/misc.go
Adds exported Company constant set to "Specter Ops, Inc." for use in resource metadata.
Dependency & tooling
go.mod
Bumps Go version, upgrades several x/* modules, adds tool directive for github.com/tc-hib/go-winres, and introduces indirect dependencies required for resource generation and tooling.

Sequence Diagram

sequenceDiagram
    participant GH as GitHub Actions
    participant CLI as generate-windows-resources
    participant FS as File System
    participant Tool as go-winres

    GH->>CLI: run CLI with version arg (Windows matrix only)
    CLI->>CLI: parse/validate version
    CLI->>CLI: build winres.json (icons, version, localized metadata)
    CLI->>FS: write winres/winres.json
    FS-->>CLI: file written
    CLI->>Tool: execute "go tool go-winres make"
    Tool->>FS: generate resource files (.syso/.res)
    FS-->>Tool: resources created
    Tool-->>CLI: return exit status
    CLI-->>GH: emit success/failure message (allowed to continue on error)
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nibbled code and chased a byte,
Made icons, versions shine so bright,
On Windows I stitched resources new,
A tiny hop — a build step through,
Specter Ops' banner in the light. 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding the winres tool to generate Windows Resources. It is concise, directly related to the core functionality being added, and provides sufficient context for a developer scanning the PR history.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BED-6446-include-version-in-exe-props

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@StranDutton
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
cmd/generate-windows-resources/generate-windows-resources.go (2)

67-76: parseProductVersion reads from the global os.Args directly.

This works, but makes the function harder to test in isolation without mutating global state (which the tests do). Consider accepting []string as a parameter for better testability. That said, it's a small build utility so this is purely a style suggestion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/generate-windows-resources/generate-windows-resources.go` around lines 67
- 76, Change parseProductVersion to accept an argument slice (e.g., args
[]string) instead of reading os.Args directly so it becomes testable; update its
signature (parseProductVersion(args []string) (string, error)), replace uses of
os.Args within the function with the provided args slice (use args[0] for the
program name and args[1] for the version), and update any call sites (main or
tests) to pass os.Args or a custom slice when testing.

111-130: Deferred f.Close() error is silently discarded.

On buffered/networked filesystems, Close can be the call that actually flushes and reports a write error. Since Encode succeeds doesn't guarantee the data hit disk. Consider checking the close error explicitly:

Suggested fix
 func writeWinresConfig(config map[string]interface{}) error {
 	if err := os.MkdirAll(winresDir, 0755); err != nil {
 		return fmt.Errorf("failed to create winres directory: %w", err)
 	}
 
 	configPath := filepath.Join(winresDir, winresJSONFile)
 	f, err := os.Create(configPath)
 	if err != nil {
 		return fmt.Errorf("failed to create %s: %w", configPath, err)
 	}
-	defer f.Close()
 
 	enc := json.NewEncoder(f)
 	enc.SetIndent("", "  ")
 	if err := enc.Encode(config); err != nil {
+		f.Close()
 		return fmt.Errorf("failed to encode JSON: %w", err)
 	}
 
-	return nil
+	return f.Close()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/generate-windows-resources/generate-windows-resources.go` around lines
111 - 130, The deferred file Close in writeWinresConfig currently discards any
error from f.Close; capture and handle the Close error so flush/write failures
aren't ignored—update writeWinresConfig to check the error returned by f.Close
(in the defer or after encoder.Encode) and return or wrap that error (e.g., if
enc.Encode succeeded but f.Close returns non-nil, return fmt.Errorf("failed to
close %s: %w", configPath, err)). Ensure you reference the same file handle (f)
and configPath and propagate the Close error instead of silently dropping it.
.github/workflows/publish.yml (1)

35-40: Double error suppression: continue-on-error: true and || echo are redundant.

The continue-on-error: true on line 37 already ensures the job won't fail if this step errors. The || echo fallback on line 40 provides a friendlier log message but is technically unnecessary for preventing failure. Not a bug—just noting the overlap. Consider keeping only one mechanism for clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish.yml around lines 35 - 40, The step named "Generate
Windows Resources" uses both continue-on-error: true and a shell fallback "||
echo 'Failed to generate Windows Resources. Skipping...'", which is redundant;
edit that step to remove one mechanism (preferably keep continue-on-error: true
and delete the "|| echo ..." suffix in the run block) so error suppression is
handled in one place and logs remain clean; look for the step name "Generate
Windows Resources" and the run command that invokes go run
cmd/generate-windows-resources/generate-windows-resources.go to make the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@go.mod`:
- Line 54: The go.mod currently pulls golang.org/x/image at an old vulnerable
revision; add an explicit module override to bump it to v0.18.0 or later
(recommended v0.36.0) to mitigate CVE-2024-24792 and ensure the indirect
consumer (go-winres) uses the fixed version; update go.mod by adding a
require/replace for golang.org/x/image with the chosen safe version and then run
go mod tidy to update the dependency graph and lockfile.

---

Nitpick comments:
In @.github/workflows/publish.yml:
- Around line 35-40: The step named "Generate Windows Resources" uses both
continue-on-error: true and a shell fallback "|| echo 'Failed to generate
Windows Resources. Skipping...'", which is redundant; edit that step to remove
one mechanism (preferably keep continue-on-error: true and delete the "|| echo
..." suffix in the run block) so error suppression is handled in one place and
logs remain clean; look for the step name "Generate Windows Resources" and the
run command that invokes go run
cmd/generate-windows-resources/generate-windows-resources.go to make the change.

In `@cmd/generate-windows-resources/generate-windows-resources.go`:
- Around line 67-76: Change parseProductVersion to accept an argument slice
(e.g., args []string) instead of reading os.Args directly so it becomes
testable; update its signature (parseProductVersion(args []string) (string,
error)), replace uses of os.Args within the function with the provided args
slice (use args[0] for the program name and args[1] for the version), and update
any call sites (main or tests) to pass os.Args or a custom slice when testing.
- Around line 111-130: The deferred file Close in writeWinresConfig currently
discards any error from f.Close; capture and handle the Close error so
flush/write failures aren't ignored—update writeWinresConfig to check the error
returned by f.Close (in the defer or after encoder.Encode) and return or wrap
that error (e.g., if enc.Encode succeeded but f.Close returns non-nil, return
fmt.Errorf("failed to close %s: %w", configPath, err)). Ensure you reference the
same file handle (f) and configPath and propagate the Close error instead of
silently dropping it.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 58c240d and d8d38a3.

⛔ Files ignored due to path filters (2)
  • go.sum is excluded by !**/*.sum
  • winres/favicon.ico is excluded by !**/*.ico
📒 Files selected for processing (6)
  • .github/workflows/build.yml
  • .github/workflows/publish.yml
  • cmd/generate-windows-resources/generate-windows-resources.go
  • cmd/generate-windows-resources/generate-windows-resources_test.go
  • constants/misc.go
  • go.mod

@StranDutton StranDutton marked this pull request as ready for review February 24, 2026 21:11
@StranDutton StranDutton changed the title BED-6446: Add winres tool to generate windows resources, tests BED-6446: Add winres tool to generate Windows Resources Feb 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant