1+ # requires -Version 7.0
2+ # requires -PSEdition Core
3+ function Invoke-TreeCommand {
4+ [CmdletBinding (SupportsShouldProcess = $true , DefaultParameterSetName = ' Run' )]
5+ param (
6+ [Parameter (Mandatory = $true , ParameterSetName = ' Run' )]
7+ [ValidateNotNullOrEmpty ()]
8+ [string ]$InputDirectory ,
9+
10+ [Parameter (Mandatory = $true , ParameterSetName = ' Run' )]
11+ [ValidateNotNullOrEmpty ()]
12+ [string ]$OutputDirectory ,
13+
14+ [Parameter (Mandatory = $true , ParameterSetName = ' Run' )]
15+ [ValidateNotNullOrEmpty ()]
16+ [string ]$FileSpec ,
17+
18+ [Parameter (Mandatory = $true , ParameterSetName = ' Run' )]
19+ [ValidateNotNullOrEmpty ()]
20+ [string ]$ExeToRun ,
21+
22+ [Parameter (Mandatory = $true , ParameterSetName = ' Run' )]
23+ [ValidateNotNullOrEmpty ()]
24+ [string ]$CLIOptions ,
25+
26+ [Parameter (ParameterSetName = ' Run' )]
27+ [switch ]$PassThru ,
28+
29+ [Parameter (ParameterSetName = ' Run' )]
30+ [switch ]$StopOnError ,
31+
32+ [Parameter (ParameterSetName = ' Run' )]
33+ [switch ]$UseCmdShell ,
34+
35+ [Parameter (Mandatory = $true , ParameterSetName = ' Version' )]
36+ [switch ]$Version
37+ )
38+
39+ $script :ToolVersion = ' 1.0.0'
40+
41+ if ($Version ) {
42+ $scriptVersion
43+ return
44+ }
45+
46+ function Expand-TreeCommandTokenList {
47+ param (
48+ [Parameter (Mandatory = $true )]
49+ [string ]$Template ,
50+
51+ [Parameter (Mandatory = $true )]
52+ [string ]$InputFileName ,
53+
54+ [Parameter (Mandatory = $true )]
55+ [string ]$OutputFileName
56+ )
57+
58+ $Result = $Template.Replace (' {InputFileName}' , $InputFileName )
59+ $Result = $Result.Replace (' {OutputFileName}' , $OutputFileName )
60+ return $Result
61+ }
62+
63+ function Get-TreeCommandResult {
64+ param (
65+ [Parameter (Mandatory = $true )]
66+ [string ]$InputFileName ,
67+
68+ [Parameter (Mandatory = $true )]
69+ [string ]$OutputFileName ,
70+
71+ [Parameter (Mandatory = $true )]
72+ [string ]$CommandLine ,
73+
74+ [AllowNull ()]
75+ [int ]$ExitCode ,
76+
77+ [AllowNull ()]
78+ [bool ]$Success ,
79+
80+ [Parameter (Mandatory = $true )]
81+ [bool ]$Executed ,
82+
83+ [Parameter (Mandatory = $true )]
84+ [bool ]$UsedCmdShell
85+ )
86+
87+ [pscustomobject ]@ {
88+ InputFileName = $InputFileName
89+ OutputFileName = $OutputFileName
90+ CommandLine = $CommandLine
91+ ExitCode = $ExitCode
92+ Success = $Success
93+ Executed = $Executed
94+ UsedCmdShell = $UsedCmdShell
95+ }
96+ }
97+
98+ try {
99+ $ResolvedInputDirectory = (Resolve-Path - Path $InputDirectory - ErrorAction Stop).Path
100+ }
101+ catch {
102+ throw " InputDirectory not found: $InputDirectory "
103+ }
104+
105+ $ResolvedInputDirectory = [System.IO.Path ]::GetFullPath($ResolvedInputDirectory )
106+ $ResolvedOutputDirectory = [System.IO.Path ]::GetFullPath($OutputDirectory )
107+
108+ $DirectorySeparator = [System.IO.Path ]::DirectorySeparatorChar
109+ if (-not $ResolvedInputDirectory.EndsWith ($DirectorySeparator )) {
110+ $ResolvedInputDirectory += $DirectorySeparator
111+ }
112+
113+ [int ]$TotalFiles = 0
114+ [int ]$Attempted = 0
115+ [int ]$Succeeded = 0
116+ [int ]$Failed = 0
117+ [int ]$Skipped = 0
118+
119+ $Files = Get-ChildItem - Path $ResolvedInputDirectory - Recurse - File - Filter $FileSpec
120+
121+ foreach ($File in $Files ) {
122+ $TotalFiles ++
123+
124+ $InputFileName = [System.IO.Path ]::GetFullPath($File.FullName )
125+
126+ if (-not $InputFileName.StartsWith ($ResolvedInputDirectory , [System.StringComparison ]::OrdinalIgnoreCase)) {
127+ Write-Warning " Skipping file outside InputDirectory boundary: $InputFileName "
128+ $Skipped ++
129+ continue
130+ }
131+
132+ $RelativePath = $InputFileName.Substring ($ResolvedInputDirectory.Length )
133+ $OutputFileName = Join-Path - Path $ResolvedOutputDirectory - ChildPath $RelativePath
134+ $OutputParentDirectory = Split-Path - Path $OutputFileName - Parent
135+
136+ $ExpandedOptions = Expand-TreeCommandTokenList `
137+ - Template $CLIOptions `
138+ - InputFileName $InputFileName `
139+ - OutputFileName $OutputFileName
140+
141+ $ExitCode = $null
142+ $Success = $null
143+ $Executed = $false
144+
145+ $CommandLine = " `" $ExeToRun `" $ExpandedOptions "
146+
147+ if ($UseCmdShell ) {
148+ $ActionDescription = " Execute via cmd.exe: $CommandLine "
149+ }
150+ else {
151+ $ActionDescription = " Execute: $CommandLine "
152+ }
153+
154+ if ($PSCmdlet.ShouldProcess ($InputFileName , $ActionDescription )) {
155+ if (-not [string ]::IsNullOrWhiteSpace($OutputParentDirectory )) {
156+ if (-not (Test-Path - LiteralPath $OutputParentDirectory )) {
157+ New-Item - ItemType Directory - Path $OutputParentDirectory - Force | Out-Null
158+ }
159+ }
160+
161+ $Attempted ++
162+ $Executed = $true
163+
164+ if ($UseCmdShell ) {
165+ & cmd.exe / d / c $CommandLine
166+ $ExitCode = $LASTEXITCODE
167+ }
168+ else {
169+ $ArgumentTokens = [System.Management.Automation.PSParser ]::Tokenize($ExpandedOptions , [ref ]$null )
170+ $ArgumentList = @ (
171+ foreach ($Token in $ArgumentTokens ) {
172+ if ($Token.Type -eq ' CommandArgument' ) {
173+ $Token.Content
174+ }
175+ }
176+ )
177+
178+ & $ExeToRun @ArgumentList
179+ $ExitCode = $LASTEXITCODE
180+ }
181+
182+ $Success = ($ExitCode -eq 0 )
183+
184+ if ($Success ) {
185+ $Succeeded ++
186+ Write-Verbose " SUCCESS ($ExitCode ): $InputFileName "
187+ }
188+ else {
189+ $Failed ++
190+ Write-Warning " FAIL ($ExitCode ): $InputFileName "
191+ }
192+ }
193+ else {
194+ $Skipped ++
195+ Write-Verbose " WHATIF: $ActionDescription "
196+ }
197+
198+ if ($PassThru ) {
199+ Get-TreeCommandResult `
200+ - InputFileName $InputFileName `
201+ - OutputFileName $OutputFileName `
202+ - CommandLine $CommandLine `
203+ - ExitCode $ExitCode `
204+ - Success $Success `
205+ - Executed $Executed `
206+ - UsedCmdShell $UseCmdShell.IsPresent
207+ }
208+
209+ if ($Executed -and (-not $Success ) -and $StopOnError ) {
210+ Write-Warning ' Stopping due to -StopOnError.'
211+ break
212+ }
213+ }
214+
215+ Write-Output ' '
216+ Write-Output ' Summary:'
217+ Write-Output " Total Files : $TotalFiles "
218+ Write-Output " Attempted : $Attempted "
219+ Write-Output " Succeeded : $Succeeded "
220+ Write-Output " Failed : $Failed "
221+ Write-Output " Skipped : $Skipped "
222+ }
0 commit comments