I accidently deleted several Powershell scripts from the command line, and recovery tools have not been successful in recovering them. As it happens, the two most important scripts were running at the time of deletion (in a normal Powershell prompt rather than ISE), and they are still running normally. Is it possible to recover these two scripts from memory?
CodePudding user response:
PowerShell caches the result every time it compiles a scriptblock from a script file, and then indexes the compiled blocks by script name/path and contents - so we can extract the raw scripts that have already been run in a given host application with a bit of reflection:
function Get-ScriptBlockCache {
# Internal variable name changed in PowerShell core, take into account
$fieldName = '_cachedScripts'
if($PSVersionTable.PSVersion.Major -ge 6){
$fieldName = 's_cachedScripts'
}
# Obtain type metadata for the field we want to extract values from
$cachedScriptsMemberInfo = [scriptblock].GetMember($fieldName, 'Static,NonPublic')[0]
# We're only interested in the keys, since they contain the original script text
return @($cachedScriptsMemberInfo.GetValue($null).Keys) |Select @{Name='Path';Expression='Item1'},@{Name='ScriptSourceCode';Expression='Item2'}
}
Now you can do:
Get-ScriptBlockCache |Where-Object Path -like "*nameOfDeletedScript.ps1" |ForEach-Object ScriptSourceCode
What if you don't have prompt access?
In case the process is running unattended (eg. as a scheduled task or run with -File
and with -NoExit
), you can use PowerShell's amazing debugging facilities to carve out a secondary runspace in the target process and run the cache recovery from there:
Enter-PSHostProcess -Id <targetProcessId>
# Once you're dropped into the target process, define the `Get-ScriptBlockCache` function and execute it
What if the process has already exited?
If the process is no longer running, then the cache is lost too - but there's another option: ScriptBlock logs!
If the machine is configured with mandatory script block logging enabled, you can also fetches any script or scriptblock PowerShell has parsed from the event log (providing the log entry hasn't been overwritten yet):
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational';Id=4104} |Select TimeCreated,@{Name='SourceCode';Expression={$_.Properties[2].Value}}
This won't show process id or script path/name, but if you know which time it executed and have a rough idea of the scripts contents, you should be able to glean it from those logs :)
Note: The script block cache extraction trick only works in ISE if you invoked the script from disk, eg & path\to\script.ps1
- scripts opened in the editor pane and executed directly within ISE will not have been cached in this fashion.