Skip to content

Instantly share code, notes, and snippets.

@sredna
Created January 31, 2022 19:51
Show Gist options
  • Save sredna/2520063ac73868a570b4aed59a118105 to your computer and use it in GitHub Desktop.
Save sredna/2520063ac73868a570b4aed59a118105 to your computer and use it in GitHub Desktop.
Add Collapse/Flatten context menu items to a directory
<#
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