Friday, January 17, 2020

Powershell - replace files in use

UPDATED - 02/08/2020

If you want to replace a file in use you need to handle a very strange registry key called "PendingFileRenameOperations" with a REG_MULTI_SZ type.

If you handle this manually you will absolutely run in trouble. As this key is rarely empty. So you would have to read, append and write again. The chance that you corrupt this thing is very high.

Better to use the builtin function for this. The API is called: MoveFileEx 
More Infos about this API you will find here:

The file will be replaced during a reboot. This is especially handy when files are locked during regular OS operation. You can even delete a locked file (check API documentation for this).

As this is an unmanaged code API (The real Win32 world without .Net extensions) you need a little bit more code to make it work like a .Net API class. So pure [.NETAPI CALL]::APINameAndOptions does not work alone!

To get a clue how to use it in PowerShell you find here an example:

# Function definition for the API MoveFileEx in PowerShell

Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices; 

    public enum MoveFileFlags
        MOVEFILE_REPLACE_EXISTING = 0x00000001,
        MOVEFILE_COPY_ALLOWED = 0x00000002,
        MOVEFILE_DELAY_UNTIL_REBOOT = 0x00000004,
        MOVEFILE_WRITE_THROUGH = 0x00000008,
        MOVEFILE_CREATE_HARDLINK = 0x00000010,

public static class Kernel32
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName,MoveFileFlags dwFlags);

# Calling the function in Powershell with an example
# Parameter Options: 
#   1. Option 1 is new fullpath filename and should have a temporary name.
#   2. Option 2 is for file in use (when API-NULL is given for option 2 then file from option 1 will be deleted)
#      IMPORTANT:  Use [NullString]::Value     the Powershell version of $Null does not get passed to the API properly!
#   3. Parameter which will tell the replacement (or even delete) of locked file during reboot!
#   4. For proper replacement you need 2 calls. 1st call with API specific NULL value will delete the original file. The 2nd call will rename the new file with temp name to the original file
#   5. To retrieve the API extended error properties in case of a failure the command is extended by $Lasterror with capturing the component model exception

# 1st Call to delete the old original file first! 
[kernel32]::MoveFileEx("C:\temp\Test1.txt",[NullString]::Value,"MOVEFILE_DELAY_UNTIL_REBOOT");$LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error()

# 2nd Call to rename the new temporary file with the old originial file name. Which now has new content!
[kernel32]::MoveFileEx("C:\temp\Test2.txt","C:\temp\Test1.txt", "MOVEFILE_DELAY_UNTIL_REBOOT");$LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error()

1. Use Sysinternals "PendMoves.exe" to check the status and valid entries of registry key for "PendingFileRenameOperations"
2. Registrykey "PendingFileRenameOperations" can be checked also manually at: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager
 a. When the key does not exist - no further rename operations exist
 b. When an operation fails during the reboot the key will be deleted also. So nothing is kept!
 c. DO NEVER EVER edit this key manually! Its a binary stored Multi_REG_SZ. Whatever you do it will just be worse!
3. For more log information's check also C:\Windows\PFRO.log

0xc0000035 indicates that you missed the delete the original file first!
0xc0000034 indicates that the file is already deleted. So you can ignore it!