1111 [switch ]$KeepEnv , # Does not override the env.g.json file.
1212 [switch ]$Claude , # Run Claude CLI instead of Build.ps1. Use -Claude for interactive, -Claude "prompt" for non-interactive.
1313 [switch ]$NoMcp , # Do not start the MCP approval server (for -Claude mode).
14+ [switch ]$Update , # Update timestamp to invalidate Docker cache and force Claude/plugin updates (Claude mode only).
1415 [string ]$ImageName , # Image name (defaults to a name based on the directory).
1516 [string ]$BuildAgentPath = $ (if ($env: TEAMCITY_JRE ) { Split-Path $env: TEAMCITY_JRE - Parent } else { ' C:\BuildAgent' }),
1617 [switch ]$LoadEnvFromKeyVault , # Forces loading environment variables form the key vault.
@@ -94,12 +95,13 @@ function New-EnvJson
9495 }
9596
9697 # Convert to JSON and save
97- $jsonPath = Join-Path $dockerContextDirectory " env.g.json"
98+ $gDirectory = Join-Path $dockerContextDirectory " .g"
99+ $jsonPath = Join-Path $gDirectory " env.g.json"
98100
99101 # Ensure the directory exists
100- if (-not (Test-Path $dockerContextDirectory ))
102+ if (-not (Test-Path $gDirectory ))
101103 {
102- New-Item - ItemType Directory - Path $dockerContextDirectory - Force | Out-Null
104+ New-Item - ItemType Directory - Path $gDirectory - Force | Out-Null
103105 }
104106
105107 # Write a test JSON file with GUID first
@@ -178,12 +180,13 @@ function New-ClaudeEnvJson
178180 $claudeEnv [" NUGET_PACKAGES" ] = $nugetPackages
179181
180182 # Convert to JSON and save
181- $jsonPath = Join-Path $dockerContextDirectory " env.g.json"
183+ $gDirectory = Join-Path $dockerContextDirectory " .g"
184+ $jsonPath = Join-Path $gDirectory " env.g.json"
182185
183186 # Ensure the directory exists
184- if (-not (Test-Path $dockerContextDirectory ))
187+ if (-not (Test-Path $gDirectory ))
185188 {
186- New-Item - ItemType Directory - Path $dockerContextDirectory - Force | Out-Null
189+ New-Item - ItemType Directory - Path $gDirectory - Force | Out-Null
187190 }
188191
189192 # Write a test JSON file with GUID first
@@ -325,6 +328,32 @@ function Copy-McpServerToTemp
325328 }
326329}
327330
331+ function Get-TimestampFile
332+ {
333+ param (
334+ [switch ]$Update
335+ )
336+
337+ $timestampDir = Join-Path $env: LOCALAPPDATA " PostSharp.Engineering"
338+ $timestampFile = Join-Path $timestampDir " update.timestamp"
339+
340+ # Ensure directory exists
341+ if (-not (Test-Path $timestampDir ))
342+ {
343+ New-Item - ItemType Directory - Path $timestampDir - Force | Out-Null
344+ }
345+
346+ # Create file if it doesn't exist OR update if -Update specified
347+ if (-not (Test-Path $timestampFile ) -or $Update )
348+ {
349+ $timestamp = [DateTime ]::UtcNow.ToString(" o" ) # ISO 8601 format
350+ Set-Content - Path $timestampFile - Value $timestamp - NoNewline - Force
351+ Write-Host " Timestamp file updated: $timestamp " - ForegroundColor Cyan
352+ }
353+
354+ return $timestampFile
355+ }
356+
328357if ($env: RUNNING_IN_DOCKER )
329358{
330359 Write-Error " Already running in Docker."
@@ -400,6 +429,12 @@ if (-not $KeepEnv)
400429 {
401430 # Use Claude-specific environment variables (filtered and renamed)
402431 New-ClaudeEnvJson
432+
433+ # Get/update timestamp file for cache invalidation (only if building image)
434+ if (-not $NoBuildImage )
435+ {
436+ $timestampFile = Get-TimestampFile - Update:$Update
437+ }
403438 }
404439 else
405440 {
@@ -444,6 +479,9 @@ if (-not (Test-Path $dockerContextDirectory))
444479}
445480
446481
482+ # Container user profile (matches actual Windows user in container)
483+ $containerUserProfile = " C:\Users\ContainerAdministrator"
484+
447485# Prepare volume mappings (stored as mapping strings, "-v" flags added later)
448486$VolumeMappings = @ (" ${SourceDirName} :${SourceDirName} " )
449487$MountPoints = @ ($SourceDirName )
@@ -480,6 +518,17 @@ if (-not $NoNuGetCache)
480518 $MountPoints += $nugetCacheDir
481519}
482520
521+ # Mount PostSharp.Engineering data directory (for version counters)
522+ $hostEngineeringDataDir = Join-Path $env: LOCALAPPDATA " PostSharp.Engineering"
523+ if (-not (Test-Path $hostEngineeringDataDir ))
524+ {
525+ New-Item - ItemType Directory - Force - Path $hostEngineeringDataDir | Out-Null
526+ }
527+
528+ $containerEngineeringDataDir = Join-Path $containerUserProfile " AppData\Local\PostSharp.Engineering"
529+ $VolumeMappings += " ${hostEngineeringDataDir} :${containerEngineeringDataDir} "
530+ $MountPoints += $containerEngineeringDataDir
531+
483532# Mount VS Remote Debugger
484533if ($StartVsmon )
485534{
@@ -660,7 +709,12 @@ if ($driveLetters.Count -gt 0)
660709}
661710
662711# Create Init.g.ps1 with git configuration (safe.directory and user identity)
663- $initScript = Join-Path $dockerContextDirectory " Init.g.ps1"
712+ $gDirectory = Join-Path $dockerContextDirectory " .g"
713+ if (-not (Test-Path $gDirectory ))
714+ {
715+ New-Item - ItemType Directory - Path $gDirectory - Force | Out-Null
716+ }
717+ $initScript = Join-Path $gDirectory " Init.g.ps1"
664718$initScriptContent = @"
665719# Auto-generated initialization script for container startup
666720
@@ -692,6 +746,19 @@ foreach (`$dir in `$gitDirectories) {
692746"@
693747$initScriptContent | Set-Content - Path $initScript - Encoding UTF8
694748
749+ # Copy timestamp file to docker context (for Claude mode cache invalidation)
750+ if ($Claude -and $timestampFile )
751+ {
752+ $gDirectory = Join-Path $dockerContextDirectory " .g"
753+ if (-not (Test-Path $gDirectory ))
754+ {
755+ New-Item - ItemType Directory - Path $gDirectory - Force | Out-Null
756+ }
757+ $timestampDestination = Join-Path $gDirectory " update.timestamp"
758+ Copy-Item - Path $timestampFile - Destination $timestampDestination - Force
759+ Write-Host " Copied timestamp file to docker context" - ForegroundColor Cyan
760+ }
761+
695762$mountPointsAsString = $MountPoints -Join " ;"
696763$gitDirectoriesAsString = $GitDirectories -Join " ;"
697764
@@ -878,7 +945,6 @@ if (-not $BuildImage)
878945
879946 # Container will have its own Claude profile (no mount, no copy from host)
880947 $hostUserProfile = $env: USERPROFILE
881- $containerUserProfile = " C:\Users\ContainerUser"
882948
883949 # Convert volume mappings to docker args format (interleave "-v" flags)
884950 $volumeArgs = @ ()
@@ -930,7 +996,7 @@ if (-not $BuildImage)
930996 {
931997 " "
932998 }
933- $inlineScript = " ${substCommandsInline} & c:\Init.g.ps1; cd '$SourceDirName '; & .\eng\RunClaude.ps1 -Prompt `" $ClaudePrompt `" $mcpArg "
999+ $inlineScript = " ${substCommandsInline} & c:\docker-context\ Init.g.ps1; cd '$SourceDirName '; & .\eng\RunClaude.ps1 -Prompt `" $ClaudePrompt `" $mcpArg "
9341000 }
9351001 else
9361002 {
@@ -944,17 +1010,14 @@ if (-not $BuildImage)
9441010 {
9451011 " "
9461012 }
947- $inlineScript = " ${substCommandsInline} & c:\Init.g.ps1; cd '$SourceDirName '; & .\eng\RunClaude.ps1$mcpArg "
1013+ $inlineScript = " ${substCommandsInline} & c:\docker-context\ Init.g.ps1; cd '$SourceDirName '; & .\eng\RunClaude.ps1$mcpArg "
9481014 }
9491015
9501016 $dockerArgsAsString = $dockerArgs -join " "
9511017 $pwshPath = ' C:\Program Files\PowerShell\7\pwsh.exe'
9521018
953- # Set HOME/USERPROFILE so Claude finds its config in the mounted location
954- $envArgs = @ (
955- " -e" , " HOME=$containerUserProfile " ,
956- " -e" , " USERPROFILE=$containerUserProfile "
957- )
1019+ # Environment variables to pass to container
1020+ $envArgs = @ ()
9581021
9591022 # Pass MCP secret to container if MCP server is running
9601023 if ($mcpSecret )
@@ -965,7 +1028,8 @@ if (-not $BuildImage)
9651028 try
9661029 {
9671030 # Start new container with docker run
968- Write-Host " Executing: docker run --rm --memory=$Memory --cpus=$Cpus --isolation=$Isolation $dockerArgsAsString $VolumeMappingsAsString -e HOME=$containerUserProfile -e USERPROFILE=$containerUserProfile -w $ContainerSourceDir $ImageTag `" $pwshPath `" -Command `" $inlineScript `" " - ForegroundColor Cyan
1031+ $envArgsAsString = ($envArgs -join " " )
1032+ Write-Host " Executing: docker run --rm --memory=$Memory --cpus=$Cpus --isolation=$Isolation $dockerArgsAsString $VolumeMappingsAsString $envArgsAsString -w $ContainerSourceDir $ImageTag `" $pwshPath `" -Command `" $inlineScript `" " - ForegroundColor Cyan
9691033 docker run -- rm -- memory= $Memory -- cpus= $Cpus -- isolation= $Isolation $dockerArgs @volumeArgs @envArgs - w $ContainerSourceDir $ImageTag $pwshPath - Command $inlineScript
9701034 $dockerExitCode = $LASTEXITCODE
9711035 }
@@ -1073,7 +1137,7 @@ if (-not $BuildImage)
10731137 $dockerArgsAsString = $dockerArgs -join " "
10741138
10751139 # Build inline script: subst drives, run init, cd to source, run build
1076- $inlineScript = " ${substCommandsInline} & c:\Init.g.ps1; cd '$SourceDirName '; & .\$Script $buildArgsString ; $pwshExitCommand "
1140+ $inlineScript = " ${substCommandsInline} & c:\docker-context\ Init.g.ps1; cd '$SourceDirName '; & .\$Script $buildArgsString ; $pwshExitCommand "
10771141
10781142 $pwshPath = ' C:\Program Files\PowerShell\7\pwsh.exe'
10791143
0 commit comments