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.
- Note that
=
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.
- For the same reason,
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
.
- As an aside: you wouldn't have that problem in PowerShell (Core) 7+, where BOM-less UTF-8 is the consistent default.
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")