Created
January 31, 2022 19:51
-
-
Save sredna/2520063ac73868a570b4aed59a118105 to your computer and use it in GitHub Desktop.
Add Collapse/Flatten context menu items to a directory
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
powershell -ExecutionPolicy RemoteSigned -NoProfile -File "CollapseFolder.ps1" Install | |
#> | |
Add-Type @" | |
using System; | |
using System.Collections.Generic; | |
using System.Runtime.InteropServices; | |
public class X | |
{ | |
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IBindCtx {} | |
public enum SIGDN : uint { SIGDN_NORMALDISPLAY = 0x00000000, SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000 } | |
[ComImport] [Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | |
public interface IShellItem | |
{ | |
[return: MarshalAs(UnmanagedType.Interface)] object BindToHandler(IBindCtx pbc, ref Guid bhid, ref Guid riid); | |
IShellItem GetParent(); | |
[return: MarshalAs(UnmanagedType.LPWStr)] string GetDisplayName(SIGDN sigdnName); | |
int GetAttributes(uint sfgaoMask); | |
int Compare(IShellItem psi, uint hint); | |
} | |
public static Guid IID_IShellItem = typeof(IShellItem).GUID; | |
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IFileOperationProgressSink {} | |
[ComImport] [Guid("947aab5f-0a5c-4c13-b4d6-4bf7836fc9f8")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | |
public interface IFileOperation | |
{ | |
uint Advise(); | |
void Unadvise(uint dwCookie); | |
[PreserveSig] int SetOperationFlags(uint dwOperationFlags); | |
void SetProgressMessage([MarshalAs(UnmanagedType.LPWStr)] string pszMessage); | |
void SetProgressDialog([MarshalAs(UnmanagedType.Interface)] object popd); | |
void SetProperties(); | |
void SetOwnerWindow(); | |
void ApplyPropertiesToItem(); | |
void ApplyPropertiesToItems(); | |
void RenameItem(); | |
void RenameItems(); | |
[PreserveSig] int MoveItem(IShellItem psiItem, IShellItem psiDestinationFolder, IntPtr NewName, IntPtr Sink); | |
void MoveItems([MarshalAs(UnmanagedType.Interface)] object punkItems, IShellItem psiDestinationFolder); | |
void CopyItem(IShellItem psiItem, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, IFileOperationProgressSink pfopsItem); | |
void CopyItems(); | |
[PreserveSig] int DeleteItem(IShellItem psiItem, IntPtr Sink); | |
void DeleteItems(); | |
int NewItem(); | |
[PreserveSig] int PerformOperations(); | |
[return: MarshalAs(UnmanagedType.Bool)] bool GetAnyOperationsAborted(); | |
} | |
[ComImport] [Guid("3AD05575-8857-4850-9277-11B85BDB8E09")] public class FileOperation { } | |
[DllImport("shell32.dll", SetLastError = false, CharSet = CharSet.Unicode)] | |
static extern int SHCreateItemFromParsingName(string Path, IntPtr pbc, ref Guid riid, out IShellItem ppv); | |
public static int AddMove(ref IFileOperation FO, string Source, string DestinationFolder) | |
{ | |
IShellItem sis, sid; | |
int hr = SHCreateItemFromParsingName(Source, IntPtr.Zero, ref IID_IShellItem, out sis); | |
if (hr < 0 ) return hr; | |
hr = SHCreateItemFromParsingName(DestinationFolder, IntPtr.Zero, ref IID_IShellItem, out sid); | |
if (hr < 0 ) return hr; | |
return FO.MoveItem(sis, sid, IntPtr.Zero, IntPtr.Zero); | |
} | |
public static int CollapseDir(ref IFileOperation FOM, ref List<string> DD, string Src, string Dst, bool Recurse) | |
{ | |
int hr = 0, tmphr; | |
Func<string, System.Collections.Generic.IEnumerable<string>> enumfunc = System.IO.Directory.EnumerateFileSystemEntries; | |
if (Recurse) | |
{ | |
foreach (var d in System.IO.Directory.EnumerateDirectories(Src)) | |
{ | |
CollapseDir(ref FOM, ref DD, d, Dst, Recurse); | |
} | |
enumfunc = System.IO.Directory.EnumerateFiles; | |
} | |
foreach (var e in enumfunc(Src)) | |
{ | |
tmphr = AddMove(ref FOM, e, Dst); | |
if (hr >= 0) hr = tmphr; | |
} | |
DD.Add(Src); | |
return hr; | |
} | |
public static int Collapse(string Dir, bool Recurse) | |
{ | |
List<string> deleteDirs = new List<string>(); | |
IFileOperation fom = (IFileOperation) new FileOperation(); | |
fom.SetOperationFlags(0x4240); | |
int hr = 0, tmphr; | |
var items = System.IO.Directory.EnumerateDirectories(Dir); | |
foreach (var d in items) | |
{ | |
tmphr = CollapseDir(ref fom, ref deleteDirs, d, Dir, Recurse); | |
if (hr >= 0) hr = tmphr; | |
} | |
if (deleteDirs.Count > 0) hr = fom.PerformOperations(); | |
if (hr >= 0) foreach (var d in deleteDirs) System.IO.Directory.Delete(d); | |
return hr; | |
} | |
} | |
"@ | |
$scriptDir = split-path -parent $MyInvocation.MyCommand.Definition | |
$scriptPath = [System.IO.Path]::GetFullPath($MyInvocation.MyCommand.Definition) | |
$mode = $false; $recurse = $false; | |
ForEach ($arg in $args) | |
{ | |
$rp = 'HKCU:\Software\Classes\Directory\Shell'; | |
$cmdbase = '"PowerShell.exe" -ExecutionPolicy RemoteSigned -NonInteractive -NoProfile -WindowStyle Hidden -File "' + $scriptPath + '" '; | |
if ($mode) | |
{ | |
[X]::Collapse($arg, $recurse); | |
} | |
elseif ($arg -eq 'INSTALL') | |
{ | |
New-Item -Path "$rp\\Collapse\\command" -Force | Out-Null | |
New-ItemProperty -Path "$rp\\Collapse\\command" -Name '(Default)' -Type 'String' -Force -Value ($cmdbase + 'One "%1"') | Out-Null | |
New-Item -Path "$rp\\Flatten\\command" -Force | Out-Null | |
New-ItemProperty -Path "$rp\\Flatten\\command" -Name '(Default)' -Type 'String' -Force -Value ($cmdbase + 'All "%1"') | Out-Null | |
Break; | |
} | |
elseif ($arg -eq 'UNINSTALL') | |
{ | |
if (Test-Path("$rp\\Collapse")) { Remove-Item "$rp\\Collapse" -Recurse -Force | Out-Null } | |
if (Test-Path("$rp\\Flatten")) { Remove-Item "$rp\\Flatten" -Recurse -Force | Out-Null } | |
Break; | |
} | |
elseif ($arg -eq 'ONE') | |
{ | |
$mode = $true; $recurse = $false; | |
} | |
elseif ($arg -eq 'ALL') | |
{ | |
$mode = $true; $recurse = $true; | |
} | |
else | |
{ | |
Write-Host 'Usage: <INSTALL|UNINSTALL|ONE|ALL> ["Path"] [..]'; | |
Break; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment