1616 [switch ]$LoadEnvFromKeyVault , # Forces loading environment variables form the key vault.
1717 [switch ]$StartVsmon , # Enable the remote debugger.
1818 [string ]$Script = ' Build.ps1' , # The build script to be executed inside Docker.
19+ [string ]$Isolation = ' process' , # Docker isolation mode (process or hyperv).
20+ [string ]$Memory = ' 8g' , # Docker memory limit.
1921 [Parameter (ValueFromRemainingArguments )]
2022 [string []]$BuildArgs # Arguments passed to `Build.ps1` within the container (or Claude prompt if -Claude is specified).
2123)
@@ -56,7 +58,7 @@ function New-EnvJson
5658 if (-not $envVariables.ContainsKey (" NUGET_PACKAGES" ))
5759 {
5860 $nugetPackages = $env: NUGET_PACKAGES
59- if ([string ]::IsNullOrEmpty($nugetPackages ))
61+ if ( [string ]::IsNullOrEmpty($nugetPackages ))
6062 {
6163 $nugetPackages = Join-Path $env: USERPROFILE " .nuget\packages"
6264 }
@@ -153,7 +155,7 @@ function New-ClaudeEnvJson
153155
154156 # Add NUGET_PACKAGES with default if not set
155157 $nugetPackages = $env: NUGET_PACKAGES
156- if ([string ]::IsNullOrEmpty($nugetPackages ))
158+ if ( [string ]::IsNullOrEmpty($nugetPackages ))
157159 {
158160 $nugetPackages = Join-Path $env: USERPROFILE " .nuget\packages"
159161 }
@@ -206,7 +208,7 @@ function Copy-McpServerToTemp
206208 if ($targetFrameworkDirs.Count -gt 1 )
207209 {
208210 Write-Warning " Multiple target framework directories found in $debugDir "
209- Write-Warning " Using the first one: $ ( $targetFrameworkDirs [0 ].Name) "
211+ Write-Warning " Using the first one: $ ( $targetFrameworkDirs [0 ].Name ) "
210212 }
211213
212214 $targetFrameworkDir = $targetFrameworkDirs [0 ].FullName
@@ -239,7 +241,7 @@ function Copy-McpServerToTemp
239241 throw " No executable (.exe) or assembly (.dll) found in $targetFrameworkDir "
240242 }
241243
242- Write-Host " Found MCP server executable: $ ( $executableFile.Name ) " - ForegroundColor Cyan
244+ Write-Host " Found MCP server executable: $ ( $executableFile.Name ) " - ForegroundColor Cyan
243245
244246 # Create temporary directory using hash of source directory
245247 # This ensures the same repo always uses the same temp path (avoiding firewall prompts)
@@ -319,7 +321,7 @@ if ($Claude -and -not $NoMcp)
319321
320322 Write-Host " Saving MCP server files before cleanup..." - ForegroundColor Cyan
321323 $mcpServerSnapshot = Copy-McpServerToTemp - SourceRootDir $PSScriptRoot
322- Write-Host " MCP server files saved to: $ ( $mcpServerSnapshot.TempDirectory ) " - ForegroundColor Cyan
324+ Write-Host " MCP server files saved to: $ ( $mcpServerSnapshot.TempDirectory ) " - ForegroundColor Cyan
323325 }
324326 catch
325327 {
@@ -409,7 +411,7 @@ if (-not $NoNuGetCache)
409411{
410412 # Use NUGET_PACKAGES from environment or default to user profile
411413 $nugetCacheDir = $env: NUGET_PACKAGES
412- if ([string ]::IsNullOrEmpty($nugetCacheDir ))
414+ if ( [string ]::IsNullOrEmpty($nugetCacheDir ))
413415 {
414416 $nugetCacheDir = Join-Path $env: USERPROFILE " .nuget\packages"
415417 }
@@ -485,7 +487,7 @@ if ($parentDir -and (Test-Path $parentDir) -and ($parentDirName -like "PostSharp
485487{
486488 Write-Host " Detected product family directory: $parentDirName " - ForegroundColor Cyan
487489 $siblingDirs = Get-ChildItem - Path $parentDir - Directory - ErrorAction SilentlyContinue |
488- Where-Object { $_.FullName -ne $SourceDirName }
490+ Where-Object { $_.FullName -ne $SourceDirName }
489491
490492 foreach ($sibling in $siblingDirs )
491493 {
@@ -503,7 +505,7 @@ $grandparentDir = Split-Path $parentDir -Parent
503505if ($grandparentDir -and (Test-Path $grandparentDir ))
504506{
505507 $engineeringDirs = Get-ChildItem - Path $grandparentDir - Directory - Filter " PostSharp.Engineering*" - ErrorAction SilentlyContinue |
506- Where-Object { $_.FullName -ne $SourceDirName }
508+ Where-Object { $_.FullName -ne $SourceDirName }
507509
508510 foreach ($engDir in $engineeringDirs )
509511 {
@@ -530,7 +532,7 @@ elseif (-not $env:IS_TEAMCITY_AGENT)
530532
531533# Handle non-C: drive letters for Docker (Windows containers only have C: by default)
532534# We mount X:\foo to C:\X\foo in the container, then use subst to create the X: drive
533- $driveLetters = @ {}
535+ $driveLetters = @ { }
534536
535537function Get-ContainerPath ($hostPath )
536538{
@@ -601,7 +603,7 @@ foreach ($letter in $driveLetters.Keys | Sort-Object)
601603}
602604if ($driveLetters.Count -gt 0 )
603605{
604- Write-Host " Drive letter mappings for container: $ ( $driveLetters.Keys -join ' , ' ) " - ForegroundColor Cyan
606+ Write-Host " Drive letter mappings for container: $ ( $driveLetters.Keys -join ' , ' ) " - ForegroundColor Cyan
605607}
606608
607609# Create Init.g.ps1 with git configuration (safe.directory and user identity)
@@ -621,7 +623,7 @@ if (`$gitUserEmail) {
621623
622624# Configure git safe.directory for all mounted directories
623625`$ gitDirectories = @(
624- $ ( ($GitDirectories | ForEach-Object { " '$_ '" }) -join " ,`n " )
626+ $ ( ($GitDirectories | ForEach-Object { " '$_ '" }) -join " ,`n " )
625627)
626628
627629foreach (`$ dir in `$ gitDirectories) {
@@ -675,13 +677,13 @@ if (-not $NoBuildImage -and -not $existingContainerId)
675677
676678 if ($Claude )
677679 {
678- $Dockerfile = " Dockerfile.claude"
680+ $Dockerfile = " Dockerfile.claude"
679681 }
680682 else
681683 {
682- $Dockerfile = " Dockerfile"
684+ $Dockerfile = " Dockerfile"
683685 }
684-
686+
685687
686688 Write-Host " Building the image with tag: $ImageTag " - ForegroundColor Green
687689 Get-Content - Raw $Dockerfile | docker build - t $ImageTag -- build-arg MOUNTPOINTS= " $mountPointsAsString " -f - $dockerContextDirectory
@@ -716,45 +718,54 @@ if (-not $BuildImage)
716718 $mcpPortFile = $null
717719 $mcpSecret = $null
718720 $mcpTempDir = $null
719- if (-not $NoMcp ) {
720- try {
721+ if (-not $NoMcp )
722+ {
723+ try
724+ {
721725 # Check if MCP server snapshot was saved before cleanup
722- if (-not $mcpServerSnapshot ) {
726+ if (-not $mcpServerSnapshot )
727+ {
723728 throw " MCP server files were not saved before cleanup. Cannot start MCP server."
724729 }
725730
726731 Write-Host " Starting MCP approval server..." - ForegroundColor Green
727- $mcpPortFile = Join-Path $env: TEMP " mcp-port-$ ( [System.Guid ]::NewGuid().ToString(' N' ).Substring(0 , 8 ) ) .txt"
732+ $mcpPortFile = Join-Path $env: TEMP " mcp-port-$ ( [System.Guid ]::NewGuid().ToString(' N' ).Substring(0 , 8 ) ) .txt"
728733
729734 # Generate 128-bit (16 byte) random secret for authentication
730735 $randomBytes = New-Object byte[] 16
731736 [Security.Cryptography.RandomNumberGenerator ]::Fill($randomBytes )
732- # Use URL-safe Base64 encoding (replace / with _, + with -, remove = )
733- $mcpSecret = [Convert ]::ToBase64String ($randomBytes ).Replace(' / ' , ' _ ' ).Replace( ' + ' , ' - ' ).TrimEnd( ' = ' )
737+ # Use hex encoding (alphanumeric only, URL-safe and command-line safe )
738+ $mcpSecret = [BitConverter ]::ToString ($randomBytes ).Replace(' - ' , ' ' ).ToLower( )
734739 Write-Host " Generated MCP authentication secret" - ForegroundColor Cyan
735740
736741 # Use the MCP server snapshot saved before cleanup
737742 $mcpServerInfo = $mcpServerSnapshot
738743 $mcpTempDir = $mcpServerInfo.TempDirectory
739744
740745 # Build the command to run in the new tab
741- if ($mcpServerInfo.IsExe ) {
746+ if ($mcpServerInfo.IsExe )
747+ {
742748 # Run executable directly
743- $mcpCommand = " & '$ ( $mcpServerInfo.ExecutablePath ) ' tools mcp-server --port-file '$mcpPortFile ' --secret '$mcpSecret '"
744- } else {
749+ $mcpCommand = " & '$ ( $mcpServerInfo.ExecutablePath ) ' tools mcp-server --port-file '$mcpPortFile ' --secret '$mcpSecret '"
750+ }
751+ else
752+ {
745753 # Run DLL with dotnet
746- $mcpCommand = " dotnet '$ ( $mcpServerInfo.ExecutablePath ) ' tools mcp-server --port-file '$mcpPortFile ' --secret '$mcpSecret '"
754+ $mcpCommand = " dotnet '$ ( $mcpServerInfo.ExecutablePath ) ' tools mcp-server --port-file '$mcpPortFile ' --secret '$mcpSecret '"
747755 }
748756
749757 # Try Windows Terminal first (wt.exe), fall back to conhost
750758 $wtPath = Get-Command wt.exe - ErrorAction SilentlyContinue
751- if ($wtPath ) {
759+ if ($wtPath )
760+ {
752761 # Open new tab in current Windows Terminal window
753762 # The -w 0 option targets the current window
754763 # Use single argument string for proper escaping
755764 $wtArgString = " -w 0 new-tab --title `" MCP Approval Server`" -- pwsh -NoExit -Command `" $mcpCommand `" "
756765 $mcpServerProcess = Start-Process - FilePath " wt.exe" - ArgumentList $wtArgString - PassThru
757- } else {
766+ }
767+ else
768+ {
758769 # Fallback: start in new console window
759770 $mcpServerProcess = Start-Process - FilePath " pwsh" `
760771 - ArgumentList " -NoExit" , " -Command" , $mcpCommand `
@@ -764,27 +775,32 @@ if (-not $BuildImage)
764775 # Wait for port file to be written (with timeout)
765776 $timeout = 30
766777 $elapsed = 0
767- while (-not (Test-Path $mcpPortFile ) -and $elapsed -lt $timeout ) {
778+ while (-not (Test-Path $mcpPortFile ) -and $elapsed -lt $timeout )
779+ {
768780 Start-Sleep - Milliseconds 500
769781 $elapsed += 0.5
770782 }
771783
772- if (-not (Test-Path $mcpPortFile )) {
784+ if (-not (Test-Path $mcpPortFile ))
785+ {
773786 throw " MCP server failed to start within $timeout seconds"
774787 }
775788
776789 $mcpPort = (Get-Content $mcpPortFile - Raw).Trim()
777790 Write-Host " MCP approval server running on port $mcpPort " - ForegroundColor Cyan
778791 }
779- catch {
792+ catch
793+ {
780794 Write-Host " ERROR: Failed to start MCP approval server: $_ " - ForegroundColor Red
781795 Write-Host " Continuing without MCP server support." - ForegroundColor Yellow
782796
783797 # Clean up on error
784- if ($mcpServerProcess -and ! $mcpServerProcess.HasExited ) {
798+ if ($mcpServerProcess -and ! $mcpServerProcess.HasExited )
799+ {
785800 Stop-Process - Id $mcpServerProcess.Id - Force - ErrorAction SilentlyContinue
786801 }
787- if ($mcpTempDir -and (Test-Path $mcpTempDir )) {
802+ if ($mcpTempDir -and (Test-Path $mcpTempDir ))
803+ {
788804 Remove-Item $mcpTempDir - Recurse - Force - ErrorAction SilentlyContinue
789805 }
790806
@@ -793,7 +809,9 @@ if (-not $BuildImage)
793809 $mcpSecret = $null
794810 $mcpTempDir = $null
795811 }
796- } else {
812+ }
813+ else
814+ {
797815 Write-Host " Skipping MCP approval server (-NoMcp specified)." - ForegroundColor Yellow
798816 }
799817
@@ -855,14 +873,28 @@ if (-not $BuildImage)
855873 {
856874 # Non-interactive mode with prompt - no -it flags
857875 $dockerArgs = @ ()
858- $mcpArg = if ($mcpPort ) { " -McpPort $mcpPort " } else { " " }
876+ $mcpArg = if ($mcpPort )
877+ {
878+ " -McpPort $mcpPort "
879+ }
880+ else
881+ {
882+ " "
883+ }
859884 $inlineScript = " ${substCommandsInline} & c:\Init.g.ps1; ${copyClaudeJsonScript} cd '$SourceDirName '; & .\eng\RunClaude.ps1 -Prompt `" $ClaudePrompt `" $mcpArg "
860885 }
861886 else
862887 {
863888 # Interactive mode - requires TTY
864889 $dockerArgs = @ (" -it" )
865- $mcpArg = if ($mcpPort ) { " -McpPort $mcpPort " } else { " " }
890+ $mcpArg = if ($mcpPort )
891+ {
892+ " -McpPort $mcpPort "
893+ }
894+ else
895+ {
896+ " "
897+ }
866898 $inlineScript = " ${substCommandsInline} & c:\Init.g.ps1; ${copyClaudeJsonScript} cd '$SourceDirName '; & .\eng\RunClaude.ps1$mcpArg "
867899 }
868900
@@ -876,58 +908,73 @@ if (-not $BuildImage)
876908 )
877909
878910 # Pass MCP secret to container if MCP server is running
879- if ($mcpSecret ) {
911+ if ($mcpSecret )
912+ {
880913 $envArgs += @ (" -e" , " MCP_APPROVAL_SERVER_TOKEN=$mcpSecret " )
881914 }
882915
883- try {
916+ try
917+ {
884918 # Start new container with docker run
885- Write-Host " Executing: docker run --rm --memory=12g $dockerArgsAsString $VolumeMappingsAsString -e HOME=$containerUserProfile -e USERPROFILE=$containerUserProfile -w $ContainerSourceDir $ImageTag `" $pwshPath `" -Command `" $inlineScript `" " - ForegroundColor Cyan
886- docker run -- rm -- memory= 12g $dockerArgs @volumeArgs @envArgs - w $ContainerSourceDir $ImageTag $pwshPath - Command $inlineScript
919+ Write-Host " Executing: docker run --rm --memory=$Memory --isolation= $Isolation $dockerArgsAsString $VolumeMappingsAsString -e HOME=$containerUserProfile -e USERPROFILE=$containerUserProfile -w $ContainerSourceDir $ImageTag `" $pwshPath `" -Command `" $inlineScript `" " - ForegroundColor Cyan
920+ docker run -- rm -- memory= $Memory -- isolation = $Isolation $dockerArgs @volumeArgs @envArgs - w $ContainerSourceDir $ImageTag $pwshPath - Command $inlineScript
887921 $dockerExitCode = $LASTEXITCODE
888922 }
889- finally {
923+ finally
924+ {
890925 # Cleanup MCP server after container exits (only if it was started)
891- if ($mcpPort ) {
926+ if ($mcpPort )
927+ {
892928 Write-Host " Stopping MCP approval server..." - ForegroundColor Cyan
893929
894930 # Find the process listening on the MCP port and kill it
895- try {
931+ try
932+ {
896933 # Find PID using netstat
897934 $netstatOutput = netstat - ano | Select-String " :$mcpPort \s" | Select-Object - First 1
898- if ($netstatOutput ) {
935+ if ($netstatOutput )
936+ {
899937 $parts = $netstatOutput.Line.Trim () -split ' \s+'
900938 $mcpPid = $parts [-1 ]
901- if ($mcpPid -and $mcpPid -match ' ^\d+$' ) {
939+ if ($mcpPid -and $mcpPid -match ' ^\d+$' )
940+ {
902941 Stop-Process - Id $mcpPid - Force - ErrorAction SilentlyContinue
903942 Write-Host " Stopped MCP server process (PID: $mcpPid )" - ForegroundColor Cyan
904943 }
905944 }
906- } catch {
945+ }
946+ catch
947+ {
907948 Write-Host " Could not stop MCP server via port lookup: $_ " - ForegroundColor Yellow
908949 }
909950
910951 # Fallback: try to find by command line
911952 $mcpProcesses = Get-Process - Name pwsh, dotnet - ErrorAction SilentlyContinue |
912- Where-Object { $_.CommandLine -like " *mcp-server*" }
953+ Where-Object { $_.CommandLine -like " *mcp-server*" }
913954
914- foreach ($proc in $mcpProcesses ) {
915- try {
955+ foreach ($proc in $mcpProcesses )
956+ {
957+ try
958+ {
916959 Stop-Process - Id $proc.Id - Force - ErrorAction SilentlyContinue
917- Write-Host " Stopped MCP server process $ ( $proc.Id ) " - ForegroundColor Cyan
918- } catch {
960+ Write-Host " Stopped MCP server process $ ( $proc.Id ) " - ForegroundColor Cyan
961+ }
962+ catch
963+ {
919964 # Process may have already exited
920965 }
921966 }
922967 }
923968
924969 # Clean up port file
925- if ($mcpPortFile -and (Test-Path $mcpPortFile )) {
970+ if ($mcpPortFile -and (Test-Path $mcpPortFile ))
971+ {
926972 Remove-Item $mcpPortFile - ErrorAction SilentlyContinue
927973 }
928974
929975 # Clean up temporary MCP server directory
930- if ($mcpTempDir -and (Test-Path $mcpTempDir )) {
976+ if ($mcpTempDir -and (Test-Path $mcpTempDir ))
977+ {
931978 Write-Host " Cleaning up temporary MCP server directory: $mcpTempDir " - ForegroundColor Cyan
932979 Remove-Item $mcpTempDir - Recurse - Force - ErrorAction SilentlyContinue
933980 }
@@ -992,8 +1039,8 @@ if (-not $BuildImage)
9921039 else
9931040 {
9941041 # Start new container with docker run
995- Write-Host " Executing: `` docker run --rm --memory=12g $dockerArgsAsString $VolumeMappingsAsString -w $ContainerSourceDir $ImageTag `" $pwshPath `" $pwshArgs -Command `" $inlineScript `" " - ForegroundColor Cyan
996- docker run -- rm -- memory= 12g $dockerArgs @volumeArgs - w $ContainerSourceDir $ImageTag $pwshPath $pwshArgs - Command $inlineScript
1042+ Write-Host " Executing: `` docker run --rm --memory=$Memory --isolation= $Isolation $dockerArgsAsString $VolumeMappingsAsString -w $ContainerSourceDir $ImageTag `" $pwshPath `" $pwshArgs -Command `" $inlineScript `" " - ForegroundColor Cyan
1043+ docker run -- rm -- memory= $Memory -- isolation = $Isolation $dockerArgs @volumeArgs - w $ContainerSourceDir $ImageTag $pwshPath $pwshArgs - Command $inlineScript
9971044 }
9981045
9991046 if ($LASTEXITCODE -ne 0 )
0 commit comments