@@ -142,9 +142,158 @@ public TempDirectory()
142142
143143 public void Dispose ( )
144144 {
145- if ( Directory . Exists ( Path ) )
145+ if ( ! Directory . Exists ( Path ) )
146+ {
147+ return ;
148+ }
149+
150+ try
146151 {
147152 Directory . Delete ( Path , true ) ;
148153 }
154+ catch ( IOException ex )
155+ {
156+ TestContext . Out . WriteLine ( $ "Failed to delete temp directory: { Path } ") ;
157+ TestContext . Out . WriteLine ( $ "Exception: { ex . Message } ") ;
158+ LogLockedFiles ( Path ) ;
159+ throw ;
160+ }
161+ }
162+
163+ static void LogLockedFiles ( string directory )
164+ {
165+ TestContext . Out . WriteLine ( "Scanning for locked files..." ) ;
166+
167+ foreach ( var file in Directory . GetFiles ( directory , "*" , SearchOption . AllDirectories ) )
168+ {
169+ if ( IsFileLocked ( file ) )
170+ {
171+ TestContext . Out . WriteLine ( $ "LOCKED: { file } ") ;
172+ var processes = GetLockingProcesses ( file ) ;
173+ foreach ( var proc in processes )
174+ {
175+ TestContext . Out . WriteLine ( $ " Locked by: { proc } ") ;
176+ }
177+ }
178+ }
179+ }
180+
181+ static bool IsFileLocked ( string filePath )
182+ {
183+ try
184+ {
185+ using var stream = new FileStream ( filePath , FileMode . Open , FileAccess . ReadWrite , FileShare . None ) ;
186+ return false ;
187+ }
188+ catch ( IOException )
189+ {
190+ return true ;
191+ }
192+ catch ( UnauthorizedAccessException )
193+ {
194+ return true ;
195+ }
196+ }
197+
198+ static List < string > GetLockingProcesses ( string filePath )
199+ {
200+ var result = new List < string > ( ) ;
201+
202+ var res = RmStartSession ( out var sessionHandle , 0 , Guid . NewGuid ( ) . ToString ( ) ) ;
203+ if ( res != 0 )
204+ {
205+ result . Add ( $ "(Failed to start Restart Manager session: error { res } )") ;
206+ return result ;
207+ }
208+
209+ try
210+ {
211+ string [ ] resources = [ filePath ] ;
212+ res = RmRegisterResources ( sessionHandle , ( uint ) resources . Length , resources , 0 , null , 0 , null ) ;
213+ if ( res != 0 )
214+ {
215+ result . Add ( $ "(Failed to register resource: error { res } )") ;
216+ return result ;
217+ }
218+
219+ uint procInfoNeeded = 0 ;
220+ uint procInfo = 0 ;
221+ uint rebootReasons = 0 ;
222+
223+ res = RmGetList ( sessionHandle , out procInfoNeeded , ref procInfo , null , ref rebootReasons ) ;
224+ if ( res == ERROR_MORE_DATA && procInfoNeeded > 0 )
225+ {
226+ var processInfo = new RM_PROCESS_INFO [ procInfoNeeded ] ;
227+ procInfo = procInfoNeeded ;
228+
229+ res = RmGetList ( sessionHandle , out procInfoNeeded , ref procInfo , processInfo , ref rebootReasons ) ;
230+ if ( res == 0 )
231+ {
232+ for ( var i = 0 ; i < procInfo ; i ++ )
233+ {
234+ try
235+ {
236+ var proc = Process . GetProcessById ( processInfo [ i ] . Process . dwProcessId ) ;
237+ result . Add ( $ "PID { proc . Id } : { proc . ProcessName } ({ proc . MainModule ? . FileName ?? "unknown path" } )") ;
238+ }
239+ catch
240+ {
241+ result . Add ( $ "PID { processInfo [ i ] . Process . dwProcessId } : { processInfo [ i ] . strAppName } (process no longer running or inaccessible)") ;
242+ }
243+ }
244+ }
245+ }
246+ else if ( res == 0 && procInfoNeeded == 0 )
247+ {
248+ result . Add ( "(No processes found via Restart Manager - file may be locked by system)" ) ;
249+ }
250+ }
251+ finally
252+ {
253+ RmEndSession ( sessionHandle ) ;
254+ }
255+
256+ if ( result . Count == 0 )
257+ {
258+ result . Add ( "(Unable to determine locking process)" ) ;
259+ }
260+
261+ return result ;
262+ }
263+
264+ const int ERROR_MORE_DATA = 234 ;
265+
266+ [ DllImport ( "rstrtmgr.dll" , CharSet = CharSet . Unicode ) ]
267+ static extern int RmStartSession ( out uint pSessionHandle , int dwSessionFlags , string strSessionKey ) ;
268+
269+ [ DllImport ( "rstrtmgr.dll" ) ]
270+ static extern int RmEndSession ( uint pSessionHandle ) ;
271+
272+ [ DllImport ( "rstrtmgr.dll" , CharSet = CharSet . Unicode ) ]
273+ static extern int RmRegisterResources ( uint pSessionHandle , uint nFiles , string [ ] ? rgsFilenames , uint nApplications , RM_UNIQUE_PROCESS [ ] ? rgApplications , uint nServices , string [ ] ? rgsServiceNames ) ;
274+
275+ [ DllImport ( "rstrtmgr.dll" ) ]
276+ static extern int RmGetList ( uint dwSessionHandle , out uint pnProcInfoNeeded , ref uint pnProcInfo , [ In , Out ] RM_PROCESS_INFO [ ] ? rgAffectedApps , ref uint lpdwRebootReasons ) ;
277+
278+ [ StructLayout ( LayoutKind . Sequential ) ]
279+ struct RM_UNIQUE_PROCESS
280+ {
281+ public int dwProcessId ;
282+ public System . Runtime . InteropServices . ComTypes . FILETIME ProcessStartTime ;
283+ }
284+
285+ [ StructLayout ( LayoutKind . Sequential , CharSet = CharSet . Unicode ) ]
286+ struct RM_PROCESS_INFO
287+ {
288+ public RM_UNIQUE_PROCESS Process ;
289+ [ MarshalAs ( UnmanagedType . ByValTStr , SizeConst = 256 ) ]
290+ public string strAppName ;
291+ [ MarshalAs ( UnmanagedType . ByValTStr , SizeConst = 64 ) ]
292+ public string strServiceShortName ;
293+ public int ApplicationType ;
294+ public uint AppStatus ;
295+ public uint TSSessionId ;
296+ [ MarshalAs ( UnmanagedType . Bool ) ]
297+ public bool bRestartable ;
149298 }
150299}
0 commit comments