I want to traverse all subdirectories and make a file called .hidden
containing all the directory's files and folders which have the Windows hidden attribute, so that they are hidden in Linux desktop environments as well (shared directories in dual-boot).
The elements of this script work in the command line, but I cannot work out why the script itself doesn't work. It just results in a single empty .hidden
file in the top parent directory.
$param1=$args[0]
Get-ChildItem -Path $param1 -Directory -Recurse | ForEach-Object {
Get-ChildItem -Path $_.FullName -Hidden -Force -Name | Out-File '.hidden'
}
The problem is that you're using '.hidden'
as the output file path, which invariably targets the same output file in the current location (directory).
Instead, you must specify a path to each directory at hand, which is accessible via $_.FullName
:
Get-ChildItem -LiteralPath $param1 -Force -Directory -Recurse |
ForEach-Object {
if ($hiddenItems = $_ | Get-ChildItem -Hidden -Force -Name) {
$hiddenItems |
Out-File -LiteralPath (Join-Path $_.FullName '.hidden') -WhatIf
}
}
Note: The -WhatIf
common parameter in the command above previews the operation. Remove -WhatIf
and re-execute once you're sure the operation will do what you want.
Note:
You can speed up the file-writing command with
Set-Content -LiteralPath (Join-Path $_.FullName '.hidden') -Value $hiddenItems
, but note that in Windows PowerShell Set-Content
uses a different character encoding by default - see the bottom section for more information.
With literal paths, it's better to use -LiteralPath
rather than -Path
, so as to prevent inadvertent interpretation of such paths as wildcard patterns.
$_ | Get-ChildItem ...
is shorthand for Get-ChildItem -LiteralPath $_.FullName ...
, for the reasons explained in this answer.
As per your later request, the above command only creates an output file for subdirectories that contain at least one hidden item, hence the if ($hiddenItems = $_ | Get-ChildItem -Hidden -Force -Name) { ... }
statement.
=
in PowerShell is always an assignment statement and that in the context of (...)
the assigned value is passed through, which in the context of a conditional such as with if
causes that value to be coerced to a [bool]
; if the value is the output from a command that happens to produce no output, $false
is implied; if there is output, Get-ChildItem
outputting at least one object implies $true
. In general, however, even non-empty output can result in $false
: see the bottom section of this answer for a summary of PowerShell's to-Boolean conversion rules.As an aside:
In PowerShell (Core) 7+, $_
rather than $_.FullName
would be sufficient in the Join-Path
call, because in .NET (Core), System.IO.DirectoryInfo
(as output by Get-ChildItem
and reflected in their .Parent
property) consistently stringify to their full path.
Get-ChildItem -LiteralPath $_
would work reliably in PowerShell (Core), but not in Windows PowerShell (see next point);$_ | Get-ChildItem
works reliably in both PowerShell editions.By contrast, in Windows PowerShell, System.IO.DirectoryInfo
instances (as well as System.IO.FileInfo
instances) situationally stringify to their name only - see this answer.
Workaround for Windows PowerShell if creation of BOM-less UTF-8 files is a must / controlling the output encoding:
In Windows PowerShell, Out-File
as well as >
default to UTF-16LE files. While you can create UTF-8 files by adding -Encoding utf8
, they will invariably have a BOM; while Set-Content
produces ANSI-encoded files by default, it also invariably creates a BOM with -Encoding utf8
.
The command below creates BOM-less UTF-8 files even in Windows PowerShell, taking advantage of the (surprising) fact that New-Item
(invariably) creates such files, as explained in detail in this answer. It also uses Unix-format LF-only newlines ("`n"
)
Get-ChildItem -LiteralPath $param1 -Force -Directory -Recurse |
ForEach-Object {
if ($hiddenItems = $_ | Get-ChildItem -Hidden -Force -Name) {
# Creates BOM-less UTF-8 files.
New-Item -Force (Join-Path $_.FullName '.hidden') -Value ($hiddenItems -join "`n")
}
}
Note: If you need your files to have a trailing newline, replace ($hiddenItems -join "`n")
with (($hiddenItems -join "`n") + "`n")