Private/IWAPG-Hlp-Debug.ps1
|
function Write-IwaDebugLog { <# .SYNOPSIS Appends a timestamped line to the debug log when debug logging is enabled. .DESCRIPTION Does nothing unless $script:IwaDebugLogPath is set (Show-IntuneWinAppUtilGUI -DebugLog). Never throws: logging must not break packaging. .PARAMETER Message Text to log; multi-line text is indented under the timestamp. #> [CmdletBinding()] param( [Parameter(Mandatory)][AllowEmptyString()][string]$Message ) if (-not $script:IwaDebugLogPath) { return } try { $stamp = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss.fff') $text = ($Message -split "`r?`n") -join ("`r`n" + (' ' * 26)) Add-Content -LiteralPath $script:IwaDebugLogPath -Value "$stamp $text" -Encoding UTF8 } catch { } } function Write-IwaDebugFileState { <# .SYNOPSIS Logs the files IntuneWinAppUtil left behind after a run, and which processes hold them open. .DESCRIPTION IntuneWinAppUtil works in a GUID-named folder under %TEMP% and removes it only when it succeeds. Lists those folders modified since the run started, plus the files in the given extra folders. No-op unless debug logging is enabled. .PARAMETER Since Start time of the packaging run. .PARAMETER Folder Extra folders to inspect (e.g. staging or output folder). #> [CmdletBinding()] param( [Parameter(Mandatory)][datetime]$Since, [string[]]$Folder = @() ) if (-not $script:IwaDebugLogPath) { return } $folders = @(Get-ChildItem -LiteralPath ([System.IO.Path]::GetTempPath()) -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$' -and $_.LastWriteTime -ge $Since.AddSeconds(-2) -and (Test-Path (Join-Path $_.FullName 'IntuneWinPackage')) } | ForEach-Object FullName) $folders += @($Folder | Where-Object { $_ -and (Test-Path -LiteralPath $_) }) if ($folders.Count -eq 0) { Write-IwaDebugLog "No IntuneWinAppUtil temp folders found." return } foreach ($dir in $folders) { $files = @(Get-ChildItem -LiteralPath $dir -Recurse -File -Force -ErrorAction SilentlyContinue) Write-IwaDebugLog ("Folder '{0}': {1} file(s)" -f $dir, $files.Count) foreach ($file in $files) { $lockers = @(Get-FileLockingProcess -Path $file.FullName) $lockText = if ($lockers.Count) { 'held by: ' + ($lockers -join ', ') } else { 'not held' } Write-IwaDebugLog (" {0} {1:N0} bytes {2} {3}" -f $file.FullName, $file.Length, $file.LastWriteTime.ToString('HH:mm:ss.fff'), $lockText) } } } function Get-FileLockingProcess { <# .SYNOPSIS Returns the processes that currently hold the given files open, via the Windows Restart Manager. .PARAMETER Path One or more file paths. .OUTPUTS [string[]] "ProcessName (PID)" entries; empty when nothing holds the files or the query fails. #> [CmdletBinding()] param( [Parameter(Mandatory)][string[]]$Path ) if (-not ('IwaRestartManager' -as [type])) { Add-Type -TypeDefinition @' using System; using System.Collections.Generic; using System.Runtime.InteropServices; public static class IwaRestartManager { [StructLayout(LayoutKind.Sequential)] struct RM_UNIQUE_PROCESS { public int dwProcessId; public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] struct RM_PROCESS_INFO { public RM_UNIQUE_PROCESS Process; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string strAppName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] public string strServiceShortName; public int ApplicationType; public uint AppStatus; public uint TSSessionId; [MarshalAs(UnmanagedType.Bool)] public bool bRestartable; } [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey); [DllImport("rstrtmgr.dll")] static extern int RmEndSession(uint pSessionHandle); [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] static extern int RmRegisterResources(uint pSessionHandle, uint nFiles, string[] rgsFilenames, uint nApplications, IntPtr rgApplications, uint nServices, string[] rgsServiceNames); [DllImport("rstrtmgr.dll")] static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded, ref uint pnProcInfo, [In, Out] RM_PROCESS_INFO[] rgAffectedApps, ref uint lpdwRebootReasons); public static List<int> GetLockingProcessIds(string[] paths) { var result = new List<int>(); uint handle; if (RmStartSession(out handle, 0, Guid.NewGuid().ToString()) != 0) { return result; } try { if (RmRegisterResources(handle, (uint)paths.Length, paths, 0, IntPtr.Zero, 0, null) != 0) { return result; } uint needed = 0, count = 0, reasons = 0; int rc = RmGetList(handle, out needed, ref count, null, ref reasons); if (rc == 234 && needed > 0) { // ERROR_MORE_DATA var infos = new RM_PROCESS_INFO[needed]; count = needed; if (RmGetList(handle, out needed, ref count, infos, ref reasons) == 0) { for (int i = 0; i < count; i++) { result.Add(infos[i].Process.dwProcessId); } } } } finally { RmEndSession(handle); } return result; } } '@ } try { foreach ($processId in [IwaRestartManager]::GetLockingProcessIds([string[]]$Path)) { $name = try { (Get-Process -Id $processId -ErrorAction Stop).ProcessName } catch { '?' } "$name ($processId)" } } catch { } } |