unable to read contents of file - powershell

I'm attempting to read the contents of a file:
$releaseNotesPath = "$(System.DefaultWorkingDirectory)\_ccp-develop\ccp\ccp\ReleaseNotes\ReleaseNotes\"
$latestReleaseNotesFile = Get-ChildItem -Path $releaseNotesPath -Filter *.txt | Select-Object FullName,Name | Sort-Object -Property Name | Select-Object -First 1
The issue occurs here:
$releaseNote = Get-Content $latestReleaseNotesFile
2021-11-14T14:29:07.0729088Z ##[error]Cannot find drive. A drive with the name '#{FullName=D' does not exist.
2021-11-14T14:29:07.1945879Z ##[error]PowerShell exited with code '1'.
What am I doing wrong?

You need to provide the file path (FullName):
$releaseNote = Get-Content $latestReleaseNotesFile.FullName

Shayki Abramczyk already answered how, I'll chime in with the why part.
So, let's see what goes on, step by step
# Assign a value to variable, simple enough
$latestReleaseNotesFile =
# Get a list of all
Get-ChildItem -Path $releaseNotesPath -Filter *.txt |
# Interested only on file full name and shortname. Here's the catch
Select-Object FullName,Name |
# Sort the results by name
Sort-Object -Property Name |
# Return the first object of collection.
Select-Object -First 1
Note that in the catch part, you are implicitly creating a new, custom Powershell object that contains two members: a fully qualified file name and short name. When you pass the custom object later to Get-Content, it doesn't know how to process the custom object. So, thus the error. Shayki's answer works, as it explicitly tells to use the FullName member that contains, well file's full name.

There's good information in the existing answers; let me summarize and complement them:
A simplified and robust reformulation of your command:
$latestReleaseNotesFile =
Get-ChildItem -LiteralPath $releaseNotesPath -Filter *.txt |
Select-Object -First 1
$releaseNote = $latestReleaseNotesFile | Get-Content
Get-ChildItem -LiteralPath parameter ensures that its argument is treated literally (verbatim), as opposed to as a wildcard expression, which is what -Path expects.
Get-ChildItem's output is already sorted by name (while this fact isn't officially documented, it is behavior that users have come to rely on, and it won't change).
By not using Select-Object FullName, Name to transform the System.IO.FileInfo instances output by Get-ChildItem to create [pscustomobject] instances with only the specified properties, the resulting object can as a whole be piped to Get-Content, where it is implicitly bound by its .PSPath property value to -LiteralPath (whose alias is -PSPath), which contains the full path (with a PowerShell provider prefix).
See this answer for details on how this pipeline-based binding works.
As for what you tried:
Get-Content $latestReleaseNotesFile
This positionally binds the value of variable $latestReleaseNotesFile to the Get-Content's -Path parameter.
Since -Path is [string[]]-typed (i.e., it accepts one or more strings; use Get-Help Get-Content to see that), $latestReleaseNotesFile's value is stringified via its .ToString() method, if necessary.
Select-Object FullName, Name
This creates [pscustomobject] instances with with .FullName and .Name properties, whose values are taken from the System.IO.FileInfo instances output by Get-ChildItem.
Stringifying a [pscustomobject] instance yields an informal, hashtable-like representation suitable only for the human observer; e.g.:
# -> '#{FullName=/path/to/foo; Name=foo})'
"$([pscustomobject] #{ FullName = '/path/to/foo'; Name = 'foo' }))"
Note: I'm using an expandable string ("...") to stringify, because calling .ToString() directly unexpectedly yields the empty string, due to a longstanding bug described in GitHub issue #6163.
Unsurprisingly, passing a string with content #{FullName=/path/to/foo; Name=foo}) is not a valid file-system path, and resulted in the error you saw.
Passing the .FullName property value instead, as shown in Shayki's answer, solves that problem:
For full robustness, it is preferable to use -LiteralPath instead of the (positionally implied) -Path
Specifically, paths that contain verbatim [ or ] will otherwise be misinterpreted as a wildcard expression.
Get-Content -LiteralPath $latestReleaseNotesFile.FullName
As shown at the top, sticking with System.IO.FileInfo instances and providing them via the pipeline implicitly binds robustly to -LiteralPath:
# Assumes that $latestReleaseNotesFile is of type [System.IO.FileInfo]
# This is the equivalent of:
# Get-Content -LiteralPath $latestReleaseNotesFile.PSPath
$latestReleaseNotesFile | Get-Content
Pitfall: One would therefore expect that passing the same type of object as an argument results in the same binding, but that is not true:
# !! NOT the same as:
# $latestReleaseNotesFile | Get-Content
# !! Instead, it is the same as:
# Get-Content -Path $latestReleaseNotesFile.ToString()
Get-Content $latestReleaseNotesFile
That is, the argument is not bound by its .PSPath property value to -LiteralPath; instead, the stringified value is bound to -Path.
In PowerShell (Core) 7+, this is typically not a problem, because System.IO.FileInfo (and System.IO.DirectoryInfo) instances consistently stringify to their full path (.FullName property value) - however, it still malfunctions for literal paths containing [ or ].
In Windows PowerShell, such instances situationally stringify to the file name (.Name) only, making malfunctioning and subtle bugs likely - see this answer.
This problematic asymmetry is discussed in GitHub issue #6057.
The following is a summary of the above with concrete guidance:
Robustly passing file-system paths to file-processing cmdlets:
Note: The following applies not just to Get-Content, but to all file-processing standard cmdlets - with the unfortunate exception of Import-Csv in Windows PowerShell, due to a bug.
as an argument:
Use -LiteralPath explicitly, because using -Path (which is also implied if neither parameter is named) interprets its argument as a wildcard expression, which notably causes literal file paths containing [ or ] to be misinterpreted.
# $pathString is assumed to be a string ([string])
# OK: -LiteralPath ensures interpretation as a literal path.
Get-Content -LiteralPath $pathString
# Same as:
# Get-Content -Path $pathString
# !! Path is treated as a *wildcard expression*.
# !! This will often not matter, but breaks with paths with [ or ]
Get-Content $pathString
Additionally, in Windows PowerShell, when passing a System.IO.FileInfo or System.IO.DirectoryInfo instance, explicitly use the .FullName (file-system-native path) or .PSPath property (includes a PowerShell provider prefix; path may be based on a PowerShell-specific drive) to ensure that its full path is used; this is no longer required in PowerShell (Core) 7+, where such instances consistently stringify to their .FullName property - see this answer.
# $fileSysInfo is assumed to be of type
# [System.IO.FileInfo] or [System.IO.DirectoryInfo].
# Required for robustness in *Windows PowerShell*, works in both editions.
Get-Content -LiteralPath $fileSysInfo.FullName
# Sufficient in *PowerShell (Core) 7+*:
Get-Content -LiteralPath $fileSysInfo
via the pipeline:
System.IO.FileInfo and System.IO.DirectoryInfo instances, such as emitted by Get-ChildItem and Get-Item, can be passed as a whole, and robustly bind to -LiteralPath via their .PSPath property values - in both PowerShell editions, so you can safely use this approach in cross-edition scripts.
# Same as:
# Get-Content -LiteralPath $fileSysInfo.PSPath
$fileSysInfo | Get-Content
This mechanism - explained in more detail in this answer - relies on a property name matching a parameter name, including the parameter's alias names. Therefore, input objects of any type that have either a .LiteralPath, a .PSPath, or, in PowerShell (Core) 7+ only, a .LP property (all alias names of the -LiteralPath parameter) are bound by that property's value.[1]
# Same as:
# Get-Content -LiteralPath C:\Windows\win.ini
[pscustomobject] #{ LiteralPath = 'C:\Windows\win.ini' } | Get-Content
By contrast, any object with a .Path property binds to the wildcard-supporting -Path parameter by that property's value.
# Same as:
# Get-Content -Path C:\Windows\win.ini
# !! Path is treated as a *wildcard expression*.
[pscustomobject] #{ Path = 'C:\Windows\win.ini' } | Get-ChildItem
Direct string input and the stringified representations of any other objects also bind to -Path.
# Same as:
# Get-Content -Path C:\Windows\win.ini
# !! Path is treated as a *wildcard expression*.
'C:\Windows\win.ini' | Get-Content
Pitfall: Therefore, feeding the lines of a text file via Get-Content to Get-ChildItem, for instance, can also malfunction with paths containing [ or ]. A simple workaround is to pass them as an argument to -LiteralPath:
Get-ChildItem -LiteralPath (Get-Content -LiteralPath Paths.txt)
[1] That this logic is only applied to pipeline input, and not also to input to the same parameter by argument is an unfortunate asymmetry discussed in GitHub issue #6057.

Related

Why does Copy-Item works with input from Get-ChildItem? [duplicate]

I'm more familiar with Bash than with Powershell and sometimes I get confused by the latter's object model.
Looking at the documentation of Get-FileHash, it seems that there are 3 ways of specifying the input:
Get-FileHash [-Path]
Get-FileHash [-LiteralPath]
Get-FileHash [-InputStream]
The first two take file names, the third a stream of data.
Now, Get-ChildItem -File seems to output System.IO.FileInfo objects, judging from what Get-Member says:
$ Get-ChildItem -File | Get-Member
TypeName: System.IO.FileInfo
And yet the pipeline Get-ChildItem -File | Get-FileHash works correctly. My question is, what is the mechanism that allows converting System.IO.FileInfo to the type of inputs expected by Get-FileHash?
The System.IO.FileInfo / System.IO.DirectoryInfo instances output by PowerShell cmdlets have a .PSPath property[*] that contains the instances' fully qualified path, which is the full file-system path prefixed by the PS provider name (e.g., Microsoft.PowerShell.Core\FileSystem::C:\windows).
File-processing cmdlets such as Get-FileHash have a -LiteralPath parameter which has an alias name of -PSPath.
Because a -LiteralPath parameter (typically) accepts input from the pipeline by property name, input objects that have a .PSPath property automatically bind to it, by virtue of the PSPath parameter alias name.
As an aside:
File-processing cmdlets also have a -Path parameter, which interprets its arguments as wildcard expressions, not as literal paths.
When you pipe path strings to such cmdlets, they bind to -Path, which notably means that they are indeed interpreted as wildcards - while this will typically not matter, because most literal paths do not contain wildcard metacharacters, it does with paths that contain [ and ], which are then misinterpreted; avoiding this misinterpretation requires escaping them as `[ and `], as shown in this answer.
Due to a bug in Windows PowerShell (since fixed in PowerShell (Core) 7+), Get-FileHash, specifically, doesn't accept strings via the pipeline - see this answer for details.
How to discover this behavior:
Via the online help topic:
Programmatically:
Note: Get-Help Get-FileHash -Parameter LiteralPath | Select-Object name, aliases, pipelineinput works too in this case, but this approach is generally restricted to target commands that come with MAML-based help files, and even those that do can have help files that are out of sync with the actual command definition.
& {
Get-Command $args[0] | % Parameters | % $args[1] |
Select-Object Name, Aliases, #{
n = 'Accepts pipeline input';
e = { $(if ($_.Attributes.ValueFromPipeline) { 'by value' }), $(if ($_.Attributes.ValueFromPipelineByPropertyName) { 'by property name' }) -join ', ' -replace '^, ' }
}
} Get-FileHash LiteralPath
Output:
Name Aliases Accepts pipeline input
---- ------- ----------------------
LiteralPath {PSPath, LP} by property name
[*] It is PowerShell's file-system provider that adds this property, among others. All PowerShell providers decorate their output items this way, such as the Microsoft.Win32.RegistryKey instances output by the registry provider. The underlying .NET types do not have it. See this answer for more information.
From the About Functions Advanced Parameters documentation, ValueFromPipelineByPropertyName argument section:
The ValueFromPipelineByPropertyName argument indicates that the
parameter accepts input from a property of a pipeline object. The
object property must have the same name or alias as the parameter.
For example, if the function has a ComputerName parameter, and the
piped object has a ComputerName property, the value of the
ComputerName property is assigned to the function's ComputerName
parameter.
Update: Originally linked the wrong source code file. The correct Get-FileHash source code is here. As #mklement0 correctly answered the Get-ChildItem cmdlet outputs an object with a PSPath property, which makes this work.
It looks like it uses the pspath property.
dir there | select pspath | get-filehash
Algorithm Hash Path
--------- ---- ----
SHA256 44723DD4D0E0D46A3C7FA8ACA254B61C27B6B5789F96177E82C80700409F1535 C:\users\...

Powershell: "remove-item $object" vs "$object | remove-item"

PS C:\Users\robert.lee> new-item C:\temp\junk\delete-me
PS C:\Users\robert.lee> $d=get-childitem C:\temp\junk -file
PS C:\Users\robert.lee> remove-item $d
/* This does NOT delete C:\temp\junk\delete-me, but deletes C:\Users\robert.lee\delete-me. */
PS C:\Users\robert.lee> $d | remove-item
/* This does delete C:\temp\junk\delete-me. */
Why remove-item $d does not know where to find the file (C:\temp\junk) while $d | remove-item does? The object $d has the full path in either case.
PS: remove-item $d does remove file in another location, as tested on PSVersion 7.2.1 on macOS Monterey:
PS /Users/robert.lee/m/Windows> $d=get-childitem /tmp/Junk2 -file
PS /Users/robert.lee/m/Windows> ls /tmp/Junk2
delete-me
PS /Users/robert.lee/m/Windows> remove-item $d
PS /Users/robert.lee/m/Windows> ls /tmp/Junk2
PS /Users/robert.lee/m/Windows>
To complement Abraham Zinala's helpful answer with some background information:
From a usability perspective, $d | Remove-Item and Remove-Item $d should work the same; the reasons they don't are (written as of PowerShell Core 7.2.3):
Argument-based parameter binding is, unfortunately, less sophisticated than pipeline-based binding:
Notably, the ValueFromPipelineByPropertyName property on the -LiteralPath parameter, whose alias is -PSPath, is only honored for input objects provided via the pipeline ($d | Remove-Item) and not also when provided as an argument (Remove-Item $d).
With pipeline input, it is the .PSPath property that PowerShell's provider cmdlets decorate their output objects with that robustly binds to -LiteralPath, because the .PSPath property value is a full, provider-qualified path - see this answer for a detailed explanation.
This binding asymmetry is the subject of GitHub issue #6057.
If you pass a file- or directory-info object as an argument and you do so positionally (i.e. without preceding the argument with the target-parameter name):
It is the wildcard-based -Path parameter that is targeted:
This is normally not a problem, but does become one if the intended-as-literal path happens to contain [ characters (e.g., c:\foo\file[1].txt), given that [ is a metacharacter in PowerShell's wildcard language.
Using a named argument that explicitly targets the -LiteralPath parameter avoids this problem (note that PowerShell (Core) 7+ allows you to shorten to -lp):
# Note: Still not robust in *Windows PowerShell* - see below.
Remove-Item -LiteralPath $d
Both -Path and -LiteralPath are [string[]]-typed, i.e. they accept an array of strings, which in argument-based parameter binding means that any non-string argument is simply stringified (loosely speaking, like calling .ToString() on it)[1]:
In Windows PowerShell - as you've experienced - this can lead to subtle but potentially destructive bugs - because System.IO.FileInfo and System.IO.DirectoryInfo instances situationally stringify to only their .Name property, not to their .FullName property - see this answer for details.
In PowerShell (Core) 7+ this is no longer a problem - stringification now consistently uses .FullName.
However, stringification can still lead to failure for items returned by a PowerShell provider other than the file-system one, such as the registry provider:
# !! The 2nd command fails, because $regItem stringifies to
# !! "HKEY_CURRENT_USER\Console" (the registry-native path),
# !! which Get-Item then misinterprets as a relative file-system path.
$regItem = Get-Item HKCU:\Console
Get-Item $regItem
The upshot:
For target cmdlets that have -Path / -LiteralPath parameters, provide arguments that are provider items, such as output by Get-ChildItem and Get-Item:
preferably via the pipeline:
# Robust, in both PowerShell editions.
Get-ChildItem C:\temp\junk -file | Remove-Item
If you must use an argument, target -LiteralPath explicitly, and use the .PSPath property:
# Robust, in both PowerShell editions.
$d = Get-ChildItem C:\temp\junk -file
Remove-Item -LiteralPath $d.PSPath
If, by contrast, you need to pass the paths of provider items to .NET APIs or external programs:
Since .PSPath values contain a PowerShell provider prefix (e.g., Microsoft.PowerShell.Core\FileSystem::) in front of the item's native path, such values aren't directly understood by outside parties.
The robust approach is to pipe a provider item to Convert-Path in order to convert it to a native path in isolation (piping again results in robust .PSPath binding to -LiteralPath):
$d | Convert-Path # -> 'C:\temp\junk\delete-me'
This also works with path strings that are based on PowerShell-only drives, e.g:
Convert-Path -LiteralPath HKCU:\Console # -> 'HKEY_CURRENT_USER\Console'
[1] For most .NET types, PowerShell indeed calls .ToString(), but requests culture-invariant formatting, where supported, via the IFormattable interface; for select types, notably arrays and [pscustomobject], it uses custom stringification in lieu of calling .ToString() - see this answer for details.
PowerShell can only do so much for you, and one thing it can't do, is guess what you're after all the time. The issue you're having comes from the parameter binding going on behind the scenes. So, the binding with Remove-Item $g only happens to the Name property from the [FileInfo] object passed to it, which will coerce the object into a string and then tie it to the current working directory binding it to Path.
Whereas, $g | Remove-Item, the binding from the pipeline is bound using a different procedure and takes the PSPath instance member and binds it to the LiteralPath which is the FullName property in your [FileInfo] object.
Still coerces the object into an object of [string], just using a different property.
Sauce for an okay understanding of ParameterBinding.
Using Trace-Command, you can get a much better understanding of this.
Long story short, use $g.FullName for both scenarios as it will output an object of [string], and both Path parameters accept the entirety of the value, by pipeline as well.

PowerShell script that accepts pipeline input?

Need a little help to tweak this script that copies the latest file to another folder:
$FilePath = "C:\Downloads\Sales 202112*.xlsx"
$DestinationPath = "C:\myFiles\"
gci -Path $FilePath -File |
Sort-Object -Property LastWriteTime -Descending |
Select FullName -First 1 |
Copy-Item $_ -Destination $DestinationPath
Not sure how to reference pipeline input for the Copy-Item command.
Thanks.
tl;dr
Get-ChildItem -Path $FilePath -File |
Sort-Object -Property LastWriteTime -Descending |
Select-Object -First 1 | # Note: No 'FullName'
Copy-Item -Destination $DestinationPath # Note: No '$_'
The simplest and most robust approach is to pipe Get-ChildItem / Get-Item output as-is to other file-processing cmdlets, which binds to the latter's -LiteralPath parameter, i.e the input file path.
As for what you tried:
The automatic $_ variable, which explicitly refers to the pipeline input object at hand, is only needed (and supported) inside script blocks ({ ... }) passed to cmdlets.
With suitable pipeline input, PowerShell implicitly binds it to a pipeline-binding parameter of the target cmdlet, which in case of the Copy-Item call above is -LiteralPath. In other words: specifying a value for the target parameter as an argument isn't necessary.
This answer explains the mechanism that binds the System.IO.FileInfo and System.IO.DirectoryInfo instances that Get-ChildItem outputs to the -LiteralPath parameter of file-processing cmdlets such as Copy-Item.
Note that, with FullName out of the picture (see below), it is indeed a System.IO.FileInfo instance that Copy-Item receives as pipeline input, because the Sort-Object ... and Select-Object -First 1 calls above pass them through without changing their type.
Selecting the FullName property in an attempt to pass only the file's full path (as a string) via the pipeline is unnecessary, and, more importantly:
It fails, unless you use -ExpandProperty FullName to ensure that only the property value is output; without it, you get an object that has a .FullName property - see this answer for more information.
Even if you fix that, the solution is - at least hypothetically - less robust than passing the System.IO.FileInfo instances as a whole: mere string input binds to the -Path rather than the -LiteralPath parameter, which means that what is by definition a literal path is interpreted as a wildcard expression, which notably causes mishandling of literal paths that contain [ characters (e.g. c:\foo\file[1].txt).
See this answer for how to inspect the specifics of the pipeline-binding parameters of any given cmdlet.

Why does "Get-ChildItem -File | Get-FileHash" work?

I'm more familiar with Bash than with Powershell and sometimes I get confused by the latter's object model.
Looking at the documentation of Get-FileHash, it seems that there are 3 ways of specifying the input:
Get-FileHash [-Path]
Get-FileHash [-LiteralPath]
Get-FileHash [-InputStream]
The first two take file names, the third a stream of data.
Now, Get-ChildItem -File seems to output System.IO.FileInfo objects, judging from what Get-Member says:
$ Get-ChildItem -File | Get-Member
TypeName: System.IO.FileInfo
And yet the pipeline Get-ChildItem -File | Get-FileHash works correctly. My question is, what is the mechanism that allows converting System.IO.FileInfo to the type of inputs expected by Get-FileHash?
The System.IO.FileInfo / System.IO.DirectoryInfo instances output by PowerShell cmdlets have a .PSPath property[*] that contains the instances' fully qualified path, which is the full file-system path prefixed by the PS provider name (e.g., Microsoft.PowerShell.Core\FileSystem::C:\windows).
File-processing cmdlets such as Get-FileHash have a -LiteralPath parameter which has an alias name of -PSPath.
Because a -LiteralPath parameter (typically) accepts input from the pipeline by property name, input objects that have a .PSPath property automatically bind to it, by virtue of the PSPath parameter alias name.
As an aside:
File-processing cmdlets also have a -Path parameter, which interprets its arguments as wildcard expressions, not as literal paths.
When you pipe path strings to such cmdlets, they bind to -Path, which notably means that they are indeed interpreted as wildcards - while this will typically not matter, because most literal paths do not contain wildcard metacharacters, it does with paths that contain [ and ], which are then misinterpreted; avoiding this misinterpretation requires escaping them as `[ and `], as shown in this answer.
Due to a bug in Windows PowerShell (since fixed in PowerShell (Core) 7+), Get-FileHash, specifically, doesn't accept strings via the pipeline - see this answer for details.
How to discover this behavior:
Via the online help topic:
Programmatically:
Note: Get-Help Get-FileHash -Parameter LiteralPath | Select-Object name, aliases, pipelineinput works too in this case, but this approach is generally restricted to target commands that come with MAML-based help files, and even those that do can have help files that are out of sync with the actual command definition.
& {
Get-Command $args[0] | % Parameters | % $args[1] |
Select-Object Name, Aliases, #{
n = 'Accepts pipeline input';
e = { $(if ($_.Attributes.ValueFromPipeline) { 'by value' }), $(if ($_.Attributes.ValueFromPipelineByPropertyName) { 'by property name' }) -join ', ' -replace '^, ' }
}
} Get-FileHash LiteralPath
Output:
Name Aliases Accepts pipeline input
---- ------- ----------------------
LiteralPath {PSPath, LP} by property name
[*] It is PowerShell's file-system provider that adds this property, among others. All PowerShell providers decorate their output items this way, such as the Microsoft.Win32.RegistryKey instances output by the registry provider. The underlying .NET types do not have it. See this answer for more information.
From the About Functions Advanced Parameters documentation, ValueFromPipelineByPropertyName argument section:
The ValueFromPipelineByPropertyName argument indicates that the
parameter accepts input from a property of a pipeline object. The
object property must have the same name or alias as the parameter.
For example, if the function has a ComputerName parameter, and the
piped object has a ComputerName property, the value of the
ComputerName property is assigned to the function's ComputerName
parameter.
Update: Originally linked the wrong source code file. The correct Get-FileHash source code is here. As #mklement0 correctly answered the Get-ChildItem cmdlet outputs an object with a PSPath property, which makes this work.
It looks like it uses the pspath property.
dir there | select pspath | get-filehash
Algorithm Hash Path
--------- ---- ----
SHA256 44723DD4D0E0D46A3C7FA8ACA254B61C27B6B5789F96177E82C80700409F1535 C:\users\...

Multiple csv files to one csv file - Powershell

I have been reading through some of the previous posts about the concept, but all the solutions i find very different from eachother.
I have multiple csv's devided over seperate folders (all with kind of the same path but the subfolders are different).
I now need to import these csv's, change the headers and export it into one single csv.
I have been trying with this but im getting a very weird error: Import-Csv : Cannot open file "C:\Windows\system32\Book1.csv"
Although my path is refering to C:\csv ?
$CSVFolder = 'C:\csv\'; #'
$OutputFile = 'C:\export\test3.csv';
$CSV= #();
Get-ChildItem -Path $CSVFolder -Filter *.csv | ForEach-Object {
$CSV += #(Import-Csv -Path $_)
}
$CSV | Export-Csv -Path $OutputFile -NoTypeInformation -Force;
I was thinking of using a datatable for the headers, but its not so clear to me at this point. Also the issue with the error is not clear for me...
As has been noted:
Import-Csv -Path $_ should be Import-Csv -Path $_.FullName in your code,
(Strictly speaking, it should be Import-Csv -LiteralPath $_.FullName, because strings passed to the -Path parameter are subject to wildcard resolution).
because the [System.IO.FileInfo] instances representing files returned by Get-ChildItem are converted to strings when they are passed to the -Path (or -LiteralPath) parameter as a command-line argument (as opposed to via the pipeline), in which case the the mere file name rather than the full path is used, if your Get-ChildItem command targets a directory in Windows PowerShell (see background information below).
A mere filename such as Book1.csv is interpreted as relative to the current directory (which happened to be C:\Windows\system32 in your case), so Import-Csv looks for file C:\Windows\system32\Book1.csv rather than the intended C:\csv\Book1.csv.
Note that piping Get-ChildItem output to cmdlets is generally not affected by this, because the .PSPath property (which PowerShell adds behind the scenes) containing the full path (including PS provider prefix) binds to the -LiteralPath parameter.
Note that as of PSv5.1.14393.693, however, this mechanism is broken for Import-Csv, due to a bug.
This is a common pitfall that occurs whenever [System.IO.FileInfo] instances are passed to cmdlets that accept file paths via [string](-array)-typed parameters as arguments.
To be safe: Always use .FullName when you pass objects received from Get-ChildItem to another command as a parameter value (as opposed to via the pipeline) to ensure that the full path is passed.
Optional background information:
This behavior is a pitfall, because it is perfectly reasonable to assume that passing a [System.IO.FileInfo] instance as-is to a command that accepts file paths works, given the object-oriented nature of PowerShell - especially, since it does work reliably when using the pipeline rather than a parameter value.
Unfortunately, the built-in cmdlets that accept file paths (-Path, -LiteralPath parameters) do so as [string]s only (there is no parameter set that accepts [System.IO.FileInfo] instances directly), and it is in the course of [System.IO.FileInfo]-to-string conversion that the problem arises.
There also wouldn't be a problem if the [System.IO.FileInfo] instances consistently evaluated to the files' full paths, which is unfortunately not the case in Windows PowerShell (this has since been fixed in PowerShell Core):
Get-ChildItem <directory> outputs [System.IO.FileInfo] instances that evaluate to file names only in a string context.
Get-ChildItem <literalFilePathOrWildCardExpr> outputs [System.IO.FileInfo] instances that evaluate to full paths.
In other words: It is only if Get-ChildItem targets a directory (folder) that the objects it returns evaluate to their file names only in a string context.
Targeting a specific file or using a wildcard expression results in full paths, by contrast; with Get-Item, that's always the case.
You simply need to 'fullname' property, instead of 'name'.
Ex:
PS /Users/adil/Downloads> gi *csv |select name
Name
----
tradesdownload.csv
PS /Users/adil/Downloads> gi *csv |select name, fullname
Name FullName
---- --------
tradesdownload.csv /Users/adil/Downloads/tradesdownload.csv
try this code. This code take all csv file, import them and take only column 1, 2, 3 and change column name to header1, header2, header3, then export all into new csv file
Get-ChildItem "C:\temp2" -Filter "*.csv" |
%{Import-Csv $_.FullName -Header header1, header3,header4} |
Export-Csv "c:\temp\result.csv" -NoTypeInformation
#a short version (for no purist)
gci "C:\temp2" -Filter "*.csv" | %{ipcsv $_.FullName -Header header1, header3,header4} | epcsv "c:\temp\result.csv" -NoType