在 Windows 10 标准打开文件对话框中,我已将视图设置为按日期顺序显示大图标缩略图。
有没有办法可以防止显示任何子文件夹?
在 Windows 的文件资源管理器中,在“查看”下有一个“按修改日期分组”选项,可将文件夹推到列表底部。在“打开文件”中是否有类似的选项,或者可能有其他方法?
答案1
答案2
在 Windows 的文件资源管理器中,在“查看”下有一个“按修改日期分组”选项,可将文件夹推到列表底部。在“打开文件”中是否有类似的选项
在常用对话框中,Sort by
可以Group by
在文件夹窗口背景上下文菜单中找到:
有没有办法可以防止显示任何子文件夹?
是的,借助一点脚本魔法。探索者保存其自身和常用对话框的视图设置:
该FFlags
条目包含以下成员FolderFlags 枚举控制视图的各个方面,其中之一是FWF_NoSubfolders
(0x80
),它抑制文件夹窗格中子文件夹的显示,但不禁止通过导航窗格导航到这些子文件夹或在地址栏中键入内容。
它很容易操作探索者观点,因为这一点电源外壳将切换显示所有当前打开的探索者窗口。您可以复制并粘贴到电源外壳控制台并按enter执行;然后Up Arrow重复enter并撤消更改:
@((New-Object -com shell.application).Windows()).ForEach{
$_.Document.FolderFlags = ($_.Document.FolderFlags -bxor 0x80)
$_.Refresh()
}
如果您关闭设置了标志的窗口,它将在下次显示文件夹时生效,因为它是已保存的视图设置的一部分。
但遗憾的是,对话框并不那么容易。我们无法像使用 shell com 对象那样访问活动对话框视图,但如果FFlags
输入之前保存过视图被修改,将在下次显示对话框时使用。但每次关闭对话框并保存其视图设置时,FFlags
都会重置为 0x01,这意味着子文件夹将在下次显示对话框时可见。
因此,为了在对话框中隐藏子文件夹并使其保留,我们在编辑注册表项后拒绝修改该项的权限。不幸的是,这也会锁定已保存视图的所有其他方面,因此您需要确保图标模式、列选择等符合您的喜好,然后再隐藏子文件夹。当我们想要恢复正常显示时,我们首先删除权限Deny
,然后将默认值恢复为FFlags
。
因此电源外壳代码应保存为.ps1
您选择的目录中的文件。然后,通过 dot-sourcing 执行脚本:
. <PathTofile>\<FileName>.ps1
这将在注册表中创建必要的上下文菜单项,并且该选项将通过背景上下文菜单或 FolderItem 上下文菜单提供。
#####################################################################################################################################################
#
# Context menu syntax:
# - Directory backgrund:
# powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File <pathToThisScript> "%V" Show|Hide Background
# - Directory (selected item):
# powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File <pathToThisScript> "%V" Show|Hide
#
#####################################################################################################################################################
#############################################################################################
#
# Pinvoke and wrapper function for SHGetNameFromIDList(). This facilitates traversing
# the BagMRU index to deterimine the property bag of the view for the target directory.
#
# Wrapper syntax: [string] GetFolderName(Byte[] IDL)
#
Add-Type @"
using System;
using System.Runtime.InteropServices;
using System.Text;
public class API
{
[DllImport("shell32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Error)]
static extern int SHGetNameFromIDList(IntPtr pidl, uint sigdnName, out StringBuilder ppszName);
static public string GetFolderName(Byte[] IDL) {
GCHandle pinnedArray = GCHandle.Alloc(IDL, GCHandleType.Pinned);
IntPtr PIDL = pinnedArray.AddrOfPinnedObject();
StringBuilder name = new StringBuilder(2048);
int result = SHGetNameFromIDList(PIDL, 0x0, out name);
pinnedArray.Free();
return name.ToString();
}
}
"@ # End Add-Type
#############################################################################################
#
# Get-NSPath: Determine the fully-qualified path within the shell namespace. In essence,
# a human-readable IDL that generally mirrors the Address bar text.
#
Function Get-NSPath ($comFolder) {
If ($comFolder.ParentFolder) {
$ParentPath = Get-NSPath $comFolder.ParentFolder
### Return from subfolder recursion:
'{0}\{1}' -f $ParentPath , $comFolder.Title
} Else {
### Return from Desktop:
$comFolder.Title
}
}
#############################################################################################
#
# Get-BagNumber: With the NSPath, locate the MRU node for the target folder and return
# the correspoinding NodeSLot (Bag #).
#
Function Get-BagNumber ($NSPath) {
$BagMRU = 'HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\BagMRU'
$MRUNode = Get-Item $BagMRU
[Byte[]]$parentIDL = [Byte[]]@()
If ($NSPath -match '\\') {
$NSPath = $NSPath -replace '^Desktop\\'
$FolderNames = $NSPath.Split('\')
ForEach ($folderName in @($FolderNames)) {
$node = @($MRUNode.GetValueNames() -match '\d+').Where{[API]::GetFolderName($parentIDL + $MRUNode.GetValue($_)) -eq $folderName}[0]
$idl = $MRUNode.GetValue($node)
$MRUNode = $MRUNode.OpenSubKey($node)
$parentIDL = $parentIDL + [Byte[]]($idl[0..($idl.Length-3)])
}
}
### Return value:
$MRUNode.GetValue('NodeSlot')
}
#############################################################################################
#
# Set-NoSubfolders: Sets or clears the FWF_NoSubfolders flag and modifies permissions
# for any ComDLg subkeys under the specified bag.
#
Function Set-NoSubfolders ([String]$BagNum , [Bool]$NoSubfolders) {
$Bags = 'HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\Bags'
$dlgCount = 0
Get-ChildItem "$Bags\$BagNum" -Recurse | where Name -match 'ComDlg\w*\\' | ForEach {
$dlgCount += 1
Write-Verbose ('{3,-15}: {0}\{1}\{2}' -f ($_.PSParentPath.Split('\')[9..11] + 'Dialog view'))
$splat = @{
'Name' = 'FFlags'
'Value' = $(If ($NoSubfolders) {0x81} Else {0x01})
}
If ($NoSubfolders) { ### Set the flag and then deny SetValue permission
Lock-KeyValues $_.PSParentPath $false
$_ | Set-ItemProperty @splat
Lock-KeyValues $_.PSParentPath $true
Write-Verbose "`tFWF_NoSubfolders set."
} Else { ### Remove "deny SetValue" permission and clear flag
Lock-KeyValues $_.PSParentPath $false
$_ | Set-ItemProperty @splat
Write-Verbose "`tFWF_NoSubfolders cleared."
}
}
If ($dlgCount -eq 0) {
Write-Warning "`n`tNo dialog views saved yet for this folder.`n"
}
}
#############################################################################################
#
# Function Lock-KeyValues: Adds or removes a "Deny SetValue" rule to the ACL of the modified key.
# The rule is added when subfolders are hidden to prevent the FFlags value from being reset to the default.
# The rule is removed when the NoSubfolders flag is cleared.
# All aspects of the view are frozen when the rule is added, so best to customize other aspects of the dialog view before hiding subfolders.
#
Function Lock-KeyValues ($keyPath , [Bool]$lock) {
$DenySetValue = [Security.AccessControl.RegistryAccessRule]::new("$env:UserDomain\$env:UserName","SetValue","ContainerInherit","InheritOnly","Deny")
$kacl = Get-Acl $keyPath
If ($Lock) {
$kacl.AddAccessRule($DenySetValue) | out-null
}
Else {
$kacl.RemoveAccessRule($DenySetValue) | out-null
}
Set-Acl -Path $keyPath -AclObject $kAcl
}
#############################################################################################
### Main
#############################################################################################
If ($Args) { ### Invoked from context menu
$VerbosePreference = 'continue'
$folderPath = $Args[0]
$Shell = New-Object -com shell.application
If ($Args.Count -eq 3) {
Write-Verbose 'Invoked from folder background...'
Write-Verbose ('{1,-15}:"{0}"' -f $folderPath, 'Folder path')
$win = @($shell.windows() | where $folderPath -eq $_.Document.Folder.Self.Path)[0]
$NSPath = Get-NSPath $win.Document.Folder
} Else {
Write-Verbose 'Invoked from FolderItem...'
Write-Verbose ('{1,-15}:"{0}"' -f $folderPath, 'folderPath')
$win = @($shell.windows() | where $folderPath -eq $_.Document.FocusedItem.Path)[0]
$NSPath = '{0}\{1}' -f (Get-NSPath $win.Document.Folder) , (Split-Path $folderPath -Leaf)
}
Write-Verbose ('{1,-15}: "{0}"' -f $win.Document.Folder.Title , 'Window title')
Write-Verbose ('{1,-15}: "{0}"' -f $NSPath , 'Namespace path')
$TargetBag = Get-BagNumber $NSPath
Write-Verbose ('{1,-15}: {0}' -f $TargetBag , 'Target bag')
If ($TargetBag) {
Write-Verbose ('{1,-15}: "{0}"' -f $Args[1], 'Action')
Set-NoSubfolders $TargetBag ($Args[1] -eq 'Hide')
}
Read-Host "`n`tDone. Press <enter> to close this window.`n"
### End context menu invocation
} Else { ### Invoked with no arguments triggers context menu item creation
If ($PSCommandPath) {
$MenuText = '&Hide/show subfolders in dialogs'
$keyRoot = 'HKCU:\SOFTWARE\Classes\Directory'
$HideShow = @{
'1' = 'Hide'
'2' = 'Show'
}
$menuItemProps = [PSCustomObject]@{
'MuiVerb' = $MenuText
'SubCommands' = ''
}
### Registry key/entry creation loop
'Background' , $null | ForEach {$_} -pv pvISBackground | ForEach {
(mkdir (Join-Path $keyRoot $_ | Join-Path -CHildPath 'shell\HSDS') -Force).PSPath
} -pv pvMenuItem | ForEach {
Set-ItemProperty -Path $_ -InputObject $menuItemProps
'1' , '2' | ForEach {
$subCmdPath = (mkdir (Join-Path $pvMenuItem "Shell\$_") -Force).PSPath
Set-ItemProperty -Path $subCmdPath -Name MuiVerb -Value "&$($HideShow[$_])"
$splat = @{
'Path' = $subCmdPath
'Name' = 'Command'
'Value' = ('powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File "{0}" "%V" {1} {2}' -f $PSCommandPath , $HideShow[$_] , $pvISBackground).Trim()
'Type' = 'ExpandString'
}
New-Item @splat -Force
}
}
Write-HOst 'Context menu entries created.'
} Else {
Write-Warning "`n`tSave this script to a .ps1 file and then invoke via dot-sourcing to install as context menu item.`n"
}
}
#############################################################################################