diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0d9c0eb..6fee961 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,20 +5,29 @@ All notable changes to this project will be documented in this file.
---
## [0.3.0] - Unreleased
+- Add `-ExeOutputDir` parameter to set the compiled executable output directory
+ via `/p:DCC_ExeOutput`
+- Add `-DcuOutputDir` parameter to set the compiled DCU output directory
+ via `/p:DCC_DcuOutput`
+- Add `-UnitSearchPath` parameter to append additional unit search paths
+ via `/p:DCC_UnitSearchPath=$(DCC_UnitSearchPath);...`, preserving paths
+ already set by the project's PropertyGroups
+[#11](https://github.com/continuous-delphi/delphi-msbuild/issues/11)
+
- Add support for passing compiler defines to MSBUILD
-[#9](https://github.com/continuous-delphi/delphi-msbuild/issues/9)
+ [#9](https://github.com/continuous-delphi/delphi-msbuild/issues/9)
## [0.2.0] - 2026-03-16
- Added `delphi-msbuild.ps1` to be a direct download on the release page
-[#5](https://github.com/continuous-delphi/delphi-msbuild/issues/5)
+ [#5](https://github.com/continuous-delphi/delphi-msbuild/issues/5)
## [0.1.0] - 2026-03-16
- Initial release of `delphi-msbuild.ps1` -- build Delphi `.dproj` projects
via MSBuild from the command line, with support for piped output from
`delphi-inspect` and automatic `rsvars.bat` environment sourcing.
-[#1](https://github.com/continuous-delphi/delphi-msbuild/issues/1)
+ [#1](https://github.com/continuous-delphi/delphi-msbuild/issues/1)
diff --git a/README.md b/README.md
index 59b4413..78a0f59 100644
--- a/README.md
+++ b/README.md
@@ -115,6 +115,59 @@ The MSBuild verbosity level. Passed to MSBuild as `/v:`.
Valid values: `quiet`, `minimal`, `normal`, `detailed`, `diagnostic`
+## -ExeOutputDir
+
+```text
+-ExeOutputDir
+```
+
+Output directory for the compiled executable or library. Passed to MSBuild as
+`/p:DCC_ExeOutput=`.
+
+When omitted, MSBuild uses the output location defined in the project's
+PropertyGroups. The result object's `.exeOutputDir` is `$null` when this
+parameter is not supplied.
+
+## -DcuOutputDir
+
+```text
+-DcuOutputDir
+```
+
+Output directory for compiled `.dcu` files. Passed to MSBuild as
+`/p:DCC_DcuOutput=`.
+
+When omitted, MSBuild uses the DCU location from the project's PropertyGroups.
+The result object's `.dcuOutputDir` is `$null` when this parameter is not
+supplied.
+
+## -UnitSearchPath
+
+```text
+-UnitSearchPath
+```
+
+Additional unit search paths appended to the project's existing unit path.
+Accepts an array of path strings. Multiple paths are joined with semicolons
+and passed as:
+
+```text
+/p:DCC_UnitSearchPath="$(DCC_UnitSearchPath);path1;path2"
+```
+
+The `$(DCC_UnitSearchPath)` prefix preserves the paths already set in the
+project's PropertyGroups. Without it, the assignment would replace them
+entirely.
+
+When omitted (or an empty array), no `/p:DCC_UnitSearchPath` argument is added.
+The result object's `.unitSearchPath` is `$null` when no paths are supplied.
+
+Example:
+
+```powershell
+-UnitSearchPath @('C:\Libs\A', 'C:\Libs\B')
+```
+
## -Define
```text
@@ -191,17 +244,20 @@ On success or build failure (exit codes 0 and 5), a single
`pscustomobject` is written to the pipeline before the script exits.
This allows downstream pipeline steps to consume the build result.
-| Property | Type | Description |
-|---------------|---------|------------------------------------------------------|
-| `projectFile` | string | Absolute path to the project file |
-| `platform` | string | Platform value used (e.g. `Win32`) |
-| `config` | string | Config value used (e.g. `Debug`) |
-| `target` | string | Target used (e.g. `Build`) |
-| `rootDir` | string | Resolved Delphi installation root |
-| `rsvarsPath` | string | Derived path to `rsvars.bat` |
-| `exitCode` | int | MSBuild process exit code |
-| `success` | bool | `$true` when `exitCode` is 0 |
-| `output` | string | Captured MSBuild output; `$null` when `-ShowOutput` |
+| Property | Type | Description |
+|------------------|----------|----------------------------------------------------------|
+| `projectFile` | string | Absolute path to the project file |
+| `platform` | string | Platform value used (e.g. `Win32`) |
+| `config` | string | Config value used (e.g. `Debug`) |
+| `target` | string | Target used (e.g. `Build`) |
+| `rootDir` | string | Resolved Delphi installation root |
+| `rsvarsPath` | string | Derived path to `rsvars.bat` |
+| `exeOutputDir` | string | Value of `-ExeOutputDir`; `$null` when not supplied |
+| `dcuOutputDir` | string | Value of `-DcuOutputDir`; `$null` when not supplied |
+| `unitSearchPath` | string[] | Value of `-UnitSearchPath`; `$null` when not supplied |
+| `exitCode` | int | MSBuild process exit code |
+| `success` | bool | `$true` when `exitCode` is 0 |
+| `output` | string | Captured MSBuild output; `$null` when `-ShowOutput` |
Note: On fata errors before MSBuild is invoked (exit codes 2, 3, 4) no result
object is emitted.
diff --git a/source/delphi-msbuild.ps1 b/source/delphi-msbuild.ps1
index 6278f00..21c442f 100644
--- a/source/delphi-msbuild.ps1
+++ b/source/delphi-msbuild.ps1
@@ -74,6 +74,17 @@ param(
[ValidateSet('quiet','minimal','normal','detailed','diagnostic')]
[string]$Verbosity = 'normal',
+ # Output directory for the compiled executable or DLL (/p:DCC_ExeOutput property).
+ [string]$ExeOutputDir,
+
+ # Output directory for compiled DCU files (/p:DCC_DcuOutput property).
+ [string]$DcuOutputDir,
+
+ # Additional unit search paths (/p:DCC_UnitSearchPath property). Multiple paths are
+ # joined with semicolons and appended to the paths already set by the project's
+ # PropertyGroups.
+ [string[]]$UnitSearchPath = @(),
+
[string[]]$Define = @(),
[switch]$ShowOutput
@@ -179,7 +190,10 @@ function Invoke-MsbuildProject {
[string]$Config,
[string]$Target,
[string]$Verbosity,
- [string[]]$Define = @(),
+ [string]$ExeOutputDir,
+ [string]$DcuOutputDir,
+ [string[]]$UnitSearchPath = @(),
+ [string[]]$Define = @(),
[switch]$ShowOutput
)
@@ -191,6 +205,14 @@ function Invoke-MsbuildProject {
"/v:$Verbosity"
)
+ if (-not [string]::IsNullOrWhiteSpace($ExeOutputDir)) { $msbuildArgs += "/p:DCC_ExeOutput=$ExeOutputDir" }
+ if (-not [string]::IsNullOrWhiteSpace($DcuOutputDir)) { $msbuildArgs += "/p:DCC_DcuOutput=$DcuOutputDir" }
+
+ if ($UnitSearchPath.Count -gt 0) {
+ $unitSearchValue = '$(DCC_UnitSearchPath);' + ($UnitSearchPath -join ';')
+ $msbuildArgs += "/p:DCC_UnitSearchPath=$unitSearchValue"
+ }
+
if ($Define.Count -gt 0) {
$defineValue = '$(DCC_Define);' + ($Define -join ';')
$msbuildArgs += "/p:DCC_Define=$defineValue"
@@ -239,25 +261,31 @@ try {
Invoke-RsvarsEnvironment -RsvarsPath $rsvarsPath
$buildResult = Invoke-MsbuildProject `
- -ProjectFile $resolvedProjectFile `
- -Platform $Platform `
- -Config $Config `
- -Target $Target `
- -Verbosity $Verbosity `
- -Define $Define `
+ -ProjectFile $resolvedProjectFile `
+ -Platform $Platform `
+ -Config $Config `
+ -Target $Target `
+ -Verbosity $Verbosity `
+ -ExeOutputDir $ExeOutputDir `
+ -DcuOutputDir $DcuOutputDir `
+ -UnitSearchPath $UnitSearchPath `
+ -Define $Define `
-ShowOutput:$ShowOutput
$resultObj = [pscustomobject]@{
- scriptVersion = $script:Version
- projectFile = $resolvedProjectFile
- platform = $Platform
- config = $Config
- target = $Target
- rootDir = $resolvedRootDir
- rsvarsPath = $rsvarsPath
- exitCode = $buildResult.ExitCode
- success = ($buildResult.ExitCode -eq 0)
- output = $buildResult.Output
+ scriptVersion = $script:Version
+ projectFile = $resolvedProjectFile
+ platform = $Platform
+ config = $Config
+ target = $Target
+ rootDir = $resolvedRootDir
+ rsvarsPath = $rsvarsPath
+ exeOutputDir = if ([string]::IsNullOrWhiteSpace($ExeOutputDir)) { $null } else { $ExeOutputDir }
+ dcuOutputDir = if ([string]::IsNullOrWhiteSpace($DcuOutputDir)) { $null } else { $DcuOutputDir }
+ unitSearchPath = if ($UnitSearchPath.Count -eq 0) { $null } else { $UnitSearchPath }
+ exitCode = $buildResult.ExitCode
+ success = ($buildResult.ExitCode -eq 0)
+ output = $buildResult.Output
}
Write-Output $resultObj
diff --git a/tests/pwsh/delphi-msbuild.Tests.ps1 b/tests/pwsh/delphi-msbuild.Tests.ps1
index 519a862..29d315e 100644
--- a/tests/pwsh/delphi-msbuild.Tests.ps1
+++ b/tests/pwsh/delphi-msbuild.Tests.ps1
@@ -26,6 +26,11 @@
Passes correct MSBuild arguments to Invoke-MsbuildExe.
Forwards -ShowOutput switch to Invoke-MsbuildExe.
Returns the result object from Invoke-MsbuildExe.
+ ExeOutputDir adds /p:DCC_ExeOutput; omitted adds nothing.
+ DcuOutputDir adds /p:DCC_DcuOutput; omitted adds nothing.
+ UnitSearchPath single entry appends with $(DCC_UnitSearchPath) prefix.
+ UnitSearchPath multiple entries joined with semicolons.
+ UnitSearchPath omitted adds no /p:DCC_UnitSearchPath argument.
Omits /p:DCC_Define when no defines are supplied.
Appends /p:DCC_Define with $(DCC_Define) prefix for a single define.
Appends /p:DCC_Define with $(DCC_Define) prefix for multiple defines.
@@ -296,6 +301,171 @@ Describe 'Invoke-MsbuildProject' {
}
+ Context 'ExeOutputDir adds /p:DCC_ExeOutput' {
+
+ BeforeAll {
+ $script:capturedArgs = $null
+ Mock Invoke-MsbuildExe {
+ $script:capturedArgs = $Arguments
+ return [pscustomobject]@{ ExitCode = 0; Output = '' }
+ }
+
+ Invoke-MsbuildProject `
+ -ProjectFile 'C:\Projects\MyApp.dproj' `
+ -Platform 'Win32' `
+ -Config 'Debug' `
+ -Target 'Build' `
+ -Verbosity 'normal' `
+ -ExeOutputDir 'C:\Build\bin'
+ }
+
+ It 'includes /p:DCC_ExeOutput=C:\Build\bin' {
+ $script:capturedArgs | Should -Contain '/p:DCC_ExeOutput=C:\Build\bin'
+ }
+
+ }
+
+ Context 'ExeOutputDir omitted adds no /p:DCC_ExeOutput argument' {
+
+ BeforeAll {
+ $script:capturedArgs = $null
+ Mock Invoke-MsbuildExe {
+ $script:capturedArgs = $Arguments
+ return [pscustomobject]@{ ExitCode = 0; Output = '' }
+ }
+
+ Invoke-MsbuildProject `
+ -ProjectFile 'C:\Projects\MyApp.dproj' `
+ -Platform 'Win32' `
+ -Config 'Debug' `
+ -Target 'Build' `
+ -Verbosity 'normal'
+ }
+
+ It 'no argument contains DCC_ExeOutput' {
+ ($script:capturedArgs | Where-Object { $_ -like '*DCC_ExeOutput*' }) | Should -BeNullOrEmpty
+ }
+
+ }
+
+ Context 'DcuOutputDir adds /p:DCC_DcuOutput' {
+
+ BeforeAll {
+ $script:capturedArgs = $null
+ Mock Invoke-MsbuildExe {
+ $script:capturedArgs = $Arguments
+ return [pscustomobject]@{ ExitCode = 0; Output = '' }
+ }
+
+ Invoke-MsbuildProject `
+ -ProjectFile 'C:\Projects\MyApp.dproj' `
+ -Platform 'Win32' `
+ -Config 'Debug' `
+ -Target 'Build' `
+ -Verbosity 'normal' `
+ -DcuOutputDir 'C:\Build\dcu'
+ }
+
+ It 'includes /p:DCC_DcuOutput=C:\Build\dcu' {
+ $script:capturedArgs | Should -Contain '/p:DCC_DcuOutput=C:\Build\dcu'
+ }
+
+ }
+
+ Context 'DcuOutputDir omitted adds no /p:DCC_DcuOutput argument' {
+
+ BeforeAll {
+ $script:capturedArgs = $null
+ Mock Invoke-MsbuildExe {
+ $script:capturedArgs = $Arguments
+ return [pscustomobject]@{ ExitCode = 0; Output = '' }
+ }
+
+ Invoke-MsbuildProject `
+ -ProjectFile 'C:\Projects\MyApp.dproj' `
+ -Platform 'Win32' `
+ -Config 'Debug' `
+ -Target 'Build' `
+ -Verbosity 'normal'
+ }
+
+ It 'no argument contains DCC_DcuOutput' {
+ ($script:capturedArgs | Where-Object { $_ -like '*DCC_DcuOutput*' }) | Should -BeNullOrEmpty
+ }
+
+ }
+
+ Context 'UnitSearchPath single entry appends with $(DCC_UnitSearchPath) prefix' {
+
+ BeforeAll {
+ $script:capturedArgs = $null
+ Mock Invoke-MsbuildExe {
+ $script:capturedArgs = $Arguments
+ return [pscustomobject]@{ ExitCode = 0; Output = '' }
+ }
+
+ Invoke-MsbuildProject `
+ -ProjectFile 'C:\Projects\MyApp.dproj' `
+ -Platform 'Win32' `
+ -Config 'Debug' `
+ -Target 'Build' `
+ -Verbosity 'normal' `
+ -UnitSearchPath @('C:\Libs\MyLib')
+ }
+
+ It 'includes /p:DCC_UnitSearchPath=$(DCC_UnitSearchPath);C:\Libs\MyLib' {
+ $script:capturedArgs | Should -Contain '/p:DCC_UnitSearchPath=$(DCC_UnitSearchPath);C:\Libs\MyLib'
+ }
+
+ }
+
+ Context 'UnitSearchPath multiple entries are joined with semicolons' {
+
+ BeforeAll {
+ $script:capturedArgs = $null
+ Mock Invoke-MsbuildExe {
+ $script:capturedArgs = $Arguments
+ return [pscustomobject]@{ ExitCode = 0; Output = '' }
+ }
+
+ Invoke-MsbuildProject `
+ -ProjectFile 'C:\Projects\MyApp.dproj' `
+ -Platform 'Win32' `
+ -Config 'Debug' `
+ -Target 'Build' `
+ -Verbosity 'normal' `
+ -UnitSearchPath @('C:\Libs\A', 'C:\Libs\B')
+ }
+
+ It 'includes /p:DCC_UnitSearchPath=$(DCC_UnitSearchPath);C:\Libs\A;C:\Libs\B' {
+ $script:capturedArgs | Should -Contain '/p:DCC_UnitSearchPath=$(DCC_UnitSearchPath);C:\Libs\A;C:\Libs\B'
+ }
+
+ }
+
+ Context 'UnitSearchPath omitted adds no /p:DCC_UnitSearchPath argument' {
+
+ BeforeAll {
+ $script:capturedArgs = $null
+ Mock Invoke-MsbuildExe {
+ $script:capturedArgs = $Arguments
+ return [pscustomobject]@{ ExitCode = 0; Output = '' }
+ }
+
+ Invoke-MsbuildProject `
+ -ProjectFile 'C:\Projects\MyApp.dproj' `
+ -Platform 'Win32' `
+ -Config 'Debug' `
+ -Target 'Build' `
+ -Verbosity 'normal'
+ }
+
+ It 'no argument contains DCC_UnitSearchPath' {
+ ($script:capturedArgs | Where-Object { $_ -like '*DCC_UnitSearchPath*' }) | Should -BeNullOrEmpty
+ }
+
+ }
+
Context 'omits /p:DCC_Define when no -Define values are supplied' {
BeforeAll {