Yeah, just do it on reboot...
So simple, eh? Can't delete the script while it's running. but once the script reboots the PC, there's just that to delete and you're done. So delete it on the reboot yeah? If only it was that simple...
Run\RunOnce
So
Microsoft have these "run and runonce" reg keys, one set for each user, one set for the machine itself. But as people have discovered,
the time at which these keys execute is between OS starting to load and when the user logon is initiated, but
this is before the desktop has loaded, which can complicate things and this has been
depreciated in Windows 10 in favour of Asynchronous commands. Also, through my own experiences, I can attest to the complications of having to set a run\runonce command in script which requires some interesting syntax skill and it's worth nothing that
the current user run\runonce keys run under the credentials of THAT user. So
if they aren't an administrator, the command will be run in a standard user context with the restrictions that apply. All in all, I found this approach buggy, prone to many unusual errors dependent on OS or machine and insufferably complex for something as simple as deleting on reboot.
How do Microsoft do it?
The PendingFileRenameOperations key is a red herring.
Installers, updates and all kinds of packages delete files on reboot and they aren't leveraging keys like this, so what's the correct approach? Well, according to
Microsoft documentation, the information for these file changes on reboot is kept in the '
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager' key under the value '
PendingFileRenameOperations' which is a '
REG_MULTI_SZ' or MultiString in PowerShell. Now this seems like a good idea, and in a basic format of source and destination, but you soon find that
the strings in the multistring array are null terminated, meaning, they need to have this at end of the string for the values to be read correctly and missing these or messing them up can
corrupt the key, causing the computer to constantly think there's pending changes. So trying to keep the null values intact while parsing the key through a PowerShell script seems daunting. Instead...
MoveFileEx!!!
THE ANSWER!!!
Null... ish... (That reddit guy's answer)
Add-Type but no Remove-Type
The Code
function Move-OnReboot([parameter(Mandatory=$True)]$Path, $Destination){
Begin{
try{
[Microsoft.PowerShell.Commands.AddType.AutoGeneratedTypes.MoveFileUtils]|Out-Null
}catch{
$memberDefinition = @’
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, int dwFlags);
‘@
Add-Type -Name MoveFileUtils -MemberDefinition $memberDefinition
}
}
Process{
$Path="$((Resolve-Path $Path).Path)"
if ($Destination){
$Destination = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Destination)
}else{$Destination = [Management.Automation.Language.NullString]::Value}
$MOVEFILE_DELAY_UNTIL_REBOOT = 0x00000004
[Microsoft.PowerShell.Commands.AddType.AutoGeneratedTypes.MoveFileUtils]::MoveFileEx($Path, $Destination, $MOVEFILE_DELAY_UNTIL_REBOOT)
}
End{}
}
Stick this in a script, module, whatever.
Pass it a path.
Pass it a destination to move to another path or omit to delete the original on reboot.
Get a boolean success or fail on return.
Files First - Bonus Material
Bonus, bonus...
My script is contained in one folder when deployed, so I made a simple compression of the function code specific for deleting the script folder and containing files only, no sub folders. Placed in a script, will schedule the parent folder and all files for deletion. Put the header at the top of your script and the footer just before the last reboot call.
# Header
$scriptPath=$MyInvocation.MyCommand.Definition
$scriptFolder=(Split-Path -Parent $scriptPath)
function D-OR([parameter(Mandatory=$True)]$Path){
$Path="$((Resolve-Path $Path).Path)"
try{
[Microsoft.PowerShell.Commands.AddType.AutoGeneratedTypes.MoveFileUtils]|Out-Null
}catch{
$memberDefinition = @’
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, int dwFlags);
‘@
Add-Type -Name MoveFileUtils -MemberDefinition $memberDefinition
}
"Attempting to delete $Path on Reboot"
[Microsoft.PowerShell.Commands.AddType.AutoGeneratedTypes.MoveFileUtils]::MoveFileEx($Path,[Management.Automation.Language.NullString]::Value,4)
}
# Footer
if(Test-Path $scriptFolder){
Get-ChildItem $scriptFolder|Select-Object -ExpandProperty FullName|ForEach-Object{D-OR $_}
D-OR $scriptFolder
}