I am having a hard time piping a file to Get-Content (version PS 5.1).
Module code:
[Cmdlet(VerbsDiagnostic.Test, "FilePiping")]
public class PSTestFilePiping : PSCmdlet
{
[Parameter(Position = 0, Mandatory = true)]
public string FileName { get; set; }
protected override void ProcessRecord()
{
WriteObject(new System.IO.FileInfo(FileName));
}
}
Commands:
$x = Test-FilePiping .\readme.txt
$y = gci .\readme.txt
(I am using a relative path for simplicity)
Both $x and $y are a FileInfo object according to GetType():
IsPublic IsSerial Name BaseType
True True FileInfo System.IO.FileSystemInfo
Yet I cannot do:
$x | gc
The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
but I can do:
$y | gc
This has me flummoxed.
Where did I go wrong?
In addition to #santiago-squarzon's helpful answer, there are two things worth noting here:
PowerShell commands like Get-Item and Get-ChildItem are not just made for the FileSystem so return information for the relevant provider, which is where values like PSPath come from.
Targeting a relative path with new FileInfo() will probably not find the file you are expecting, so we should get the full path just in case.
This can be achieved in your cmdlet so that it returns the PSPath property necessary for Get-Content.
Sample:
dotnet new classlib -o PSSample -n PSSample
cd PSSample
dotnet add package PowerShellStandard.Library --version 5.1.0
code . # I am using VSC - open your editor in the directory.
# Set target framework to netstandard2.0 in .csproj
# Copy and paste below in to Class1.cs
dotnet restore
dotnet build
ipmo .\bin\Debug\netstandard2.0\PSSample.dll
Test-FilePiping -FileName .\Class1.cs | FL *
Test-FilePiping -FileName .\Class1.cs | Get-Content
Cmdlet class:
using System;
using System.IO;
using System.Linq;
using System.Management.Automation;
namespace PSSample
{
[Cmdlet(VerbsDiagnostic.Test, "FilePiping")]
public class PSTestFilePiping : PSCmdlet
{
[Parameter(Position = 0, Mandatory = true)]
public string FileName { get; set; }
protected override void ProcessRecord()
{
string filePath = GetRelativePath(FileName);
var item = InvokeProvider.Item.Get(filePath);
WriteObject(item, true); // true enumerates the collection, because InvokeProvider.Item.Get() returns a collection.
}
protected string GetRelativePath(string path)
{
string currentDir = GetVariableValue("PWD").ToString();
if (Path.IsPathRooted(path) == true || path.StartsWith("\\", StringComparison.CurrentCulture))
{
// Nothing to see here.
}
else
{
if (path == ".")
{
path = currentDir;
}
else if (path.StartsWith("..", StringComparison.CurrentCulture))
{
path = Path.Combine(
string.Join("\\",
currentDir.Split('\\').Take(currentDir.Split('\\').Count() - path.Split('\\').Count(p => p == "..")).ToArray()
),
string.Join("\\", path.Split('\\').Where(f => f != "..").ToArray()));
}
else if (path.StartsWith(".", StringComparison.CurrentCulture))
{
path = Path.Combine(currentDir, path.Substring(2));
}
else
{
path = Path.Combine(currentDir, path);
}
}
return path;
}
}
}
Output:
PS C:\Code\PSSample> Test-FilePiping -FileName .\Class1.cs | FL *
PSPath : Microsoft.PowerShell.Core\FileSystem::C:\Code\PSSample\Class1.cs
PSParentPath : Microsoft.PowerShell.Core\FileSystem::C:\Code\PSSample
PSChildName : Class1.cs
PSDrive : C
PSProvider : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
Mode : -a----
VersionInfo : File: C:\Code\PSSample\Class1.cs
InternalName:
OriginalFilename:
FileVersion:
FileDescription:
Product:
ProductVersion:
Debug: False
Patched: False
PreRelease: False
PrivateBuild: False
SpecialBuild: False
Language:
BaseName : Class1
Target : {}
LinkType :
Name : Class1.cs
Length : 1947
DirectoryName : C:\Code\PSSample
Directory : C:\Code\PSSample
IsReadOnly : False
Exists : True
FullName : C:\Code\PSSample\Class1.cs
Extension : .cs
CreationTime : 02/05/2021 13:03:22
CreationTimeUtc : 02/05/2021 12:03:22
LastAccessTime : 02/05/2021 13:04:13
LastAccessTimeUtc : 02/05/2021 12:04:13
LastWriteTime : 02/05/2021 13:04:00
LastWriteTimeUtc : 02/05/2021 12:04:00
Attributes : Archive
PS C:\Code\PSSample> Test-FilePiping -FileName .\Class1.cs | Get-Content
using System;
using System.IO;
using System.Linq;
using System.Management.Automation;
namespace PSSample
{
[Cmdlet(VerbsDiagnostic.Test, "FilePiping")]
public class PSTestFilePiping : PSCmdlet
{
[Parameter(Position = 0, Mandatory = true)]
public string FileName { get; set; }
protected override void ProcessRecord()
{
string filePath = GetRelativePath(FileName);
var item = InvokeProvider.Item.Get(filePath);
WriteObject(item, true); // true enumerates the collection, because InvokeProvider.Item.Get() returns a collection.
}
protected string GetRelativePath(string path)
{
string currentDir = GetVariableValue("PWD").ToString();
if (Path.IsPathRooted(path) == true || path.StartsWith("\\", StringComparison.CurrentCulture))
{
// Nothing to see here.
}
else
{
if (path == ".")
{
path = currentDir;
}
else if (path.StartsWith("..", StringComparison.CurrentCulture))
{
path = Path.Combine(
string.Join("\\",
currentDir.Split('\\').Take(currentDir.Split('\\').Count() - path.Split('\\').Count(p => p == "..")).ToArray()
),
string.Join("\\", path.Split('\\').Where(f => f != "..").ToArray()));
}
else if (path.StartsWith(".", StringComparison.CurrentCulture))
{
path = Path.Combine(currentDir, path.Substring(2));
}
else
{
path = Path.Combine(currentDir, path);
}
}
return path;
}
}
}
To complement the existing, helpful answers with some background information:
It is the PowerShell FileSystem provider that decorates the System.IO.FileSystemInfo instances (both System.IO.FileInfo and System.IO.DirectoryInfo) that Get-ChildItem and Get-Item emit with additional properties such as PSPath (among several others [1]), using PowerShell's ETS (Extended Type System).
The .PSPath property values of pipeline input objects are what bind to the -LiteralPath parameter of cmdlets such as Get-Content, due to said parameter being declared with the ValueFromPipelineByPropertyName property and LiteralPath being decorated with an [Alias("PSPath")] attribute; that is, PSPath is an alias (another name for) LiteralPath.
As of this writing, the PowerShell providers add these ETS properties as instance properties, which is the reason that a directly constructed System.IO.FileInfo does not have these properties, resulting in the symptom you saw (the absence of the .PSPath property prevented binding to the -LiteralPath parameter).
If the providers were to use type-level ETS properties (associated with the .NET type itself rather than with specific instances of it), this problem would go away, as such properties would then surface irrespective of how an instance was constructed.
See GitHub issue #4347 for a discussion.
[1] The full list of added properties is: PSPath, PSParentPath, PSChildName, PSDrive, PSProvider, PSIsContainer,
Mode, BaseName, Target, LinkType.
Hopefully this helps you identify your error:
PS /> 'Hello world!' > test.txt
PS /> $reader = [System.IO.FileInfo]::new('test.txt')
If I try to pipe this object directly to Get-Content it will throw with the same error you're getting:
Using this:
PS /> $reader | Get-Content
Or this:
PS /> $reader.FullName | Get-Content
Results in:
Get-Content: The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
This is because none of the properties returned by [System.IO.FileInfo] matches those paremeters accepted as ValueFromPipelineByProperyName by Get-Content:
PS /> $reader | gm -MemberType Property
TypeName: System.IO.FileInfo
Name MemberType Definition
---- ---------- ----------
Attributes Property System.IO.FileAttributes Attributes {get;set;}
CreationTime Property datetime CreationTime {get;set;}
CreationTimeUtc Property datetime CreationTimeUtc {get;set;}
Directory Property System.IO.DirectoryInfo Directory {get;}
DirectoryName Property string DirectoryName {get;}
Exists Property bool Exists {get;}
Extension Property string Extension {get;}
FullName Property string FullName {get;}
IsReadOnly Property bool IsReadOnly {get;set;}
LastAccessTime Property datetime LastAccessTime {get;set;}
LastAccessTimeUtc Property datetime LastAccessTimeUtc {get;set;}
LastWriteTime Property datetime LastWriteTime {get;set;}
LastWriteTimeUtc Property datetime LastWriteTimeUtc {get;set;}
Length Property long Length {get;}
Name Property string Name {get;}
However if we create an object with any of the properties I mentioned in my comment, Get-Content will accept it as input and read it without any problems:
PS /> [PSCustomObject]#{ Path = $reader.FullName } | Get-Content
Hello world!
PS /> [PSCustomObject]#{ LiteralPath = $reader.FullName } | Get-Content
Hello world!
PS /> [PSCustomObject]#{ PSPath = $reader.FullName } | Get-Content
Hello world!
Get-ChildItem works fine because the object returned has the PSPath property.
The best way to identify which parameters are accepted as ValueFromPipelineByProperyName is to rely on MS Docs:
-LiteralPath
Type
String[]
Aliases
PSPath, LP
Position
Named
Default value
None
Accept pipeline input
True
Accept wildcard characters
False
Related
I want to create a loop function that looks like this:
function Loop { foreach ($File in Get-ChildItem | Select-Object -exp Name) { # Some command } }
It works perfectly fine for simple one-word commands, but it doesn't work for anything else. Of course, I need a way to parse all given arguments, but I don't know how. All documentations that I've been able to understand (I just started working with PowerShell about a week ago) don't seem to have an answer to this.
It would be great if someone could give me a solution to this problem.🙂
Like issuing several commands inside the loop?
#!/usr/bin/env powershell
function script:Loop {
foreach ($File in Get-ChildItem | Select-Object -ExpandProperty Name) {
##Some command
##another command
## a test
Write-Output -InputObject ('The current item is named {0}.' -f $file)
}
}
Loop
output
PS C:\Powershell\Scripts> . "c:\Powershell\Scripts\forloop.ps1"
The current item is named dchero.ps1.
The current item is named findinstaller.ps1.
The current item is named Untitled11.ps1.
The current item is named Untitled5.ps1.
The current item is named Untitled8.ps1.
However you don't really need to do the Select-Object -ExpandProperty Name because Get-ChildItem returns multiple properties for the objects it finds. You can see all of them by piping into Get-Member
PS C:\Powershell\Scripts> Get-ChildItem | Get-Member
TypeName: System.IO.FileInfo
Name MemberType Definition
---- ---------- ----------
Target AliasProperty Target = LinkTarget
LinkType CodeProperty System.String LinkType{get=GetLinkType;}
Mode CodeProperty System.String Mode{get=Mode;}
ModeWithoutHardLink CodeProperty System.String ModeWithoutHardLink{get=ModeWithoutHardLink;}
AppendText Method System.IO.StreamWriter AppendText()
CopyTo Method System.IO.FileInfo CopyTo(string destFileName), System.IO.FileInfo CopyTo(string destFileName, bool overwrite)
Create Method System.IO.FileStream Create()
CreateAsSymbolicLink Method void CreateAsSymbolicLink(string pathToTarget)
CreateText Method System.IO.StreamWriter CreateText()
Decrypt Method void Decrypt()
Delete Method void Delete()
Encrypt Method void Encrypt()
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetLifetimeService Method System.Object GetLifetimeService()
GetObjectData Method void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context), void ISerializab…
GetType Method type GetType()
InitializeLifetimeService Method System.Object InitializeLifetimeService()
MoveTo Method void MoveTo(string destFileName), void MoveTo(string destFileName, bool overwrite)
Open Method System.IO.FileStream Open(System.IO.FileMode mode), System.IO.FileStream Open(System.IO.FileMode mode, System.IO.FileAccess access), System.IO.F…
OpenRead Method System.IO.FileStream OpenRead()
OpenText Method System.IO.StreamReader OpenText()
OpenWrite Method System.IO.FileStream OpenWrite()
Refresh Method void Refresh()
Replace Method System.IO.FileInfo Replace(string destinationFileName, string destinationBackupFileName), System.IO.FileInfo Replace(string destinationFileName,…
ResolveLinkTarget Method System.IO.FileSystemInfo ResolveLinkTarget(bool returnFinalTarget)
ToString Method string ToString()
PSChildName NoteProperty string PSChildName=dchero.ps1
PSDrive NoteProperty PSDriveInfo PSDrive=C
PSIsContainer NoteProperty bool PSIsContainer=False
PSParentPath NoteProperty string PSParentPath=Microsoft.PowerShell.Core\FileSystem::C:\Powershell\Scripts
PSPath NoteProperty string PSPath=Microsoft.PowerShell.Core\FileSystem::C:\Powershell\Scripts\dchero.ps1
PSProvider NoteProperty ProviderInfo PSProvider=Microsoft.PowerShell.Core\FileSystem
Attributes Property System.IO.FileAttributes Attributes {get;set;}
CreationTime Property datetime CreationTime {get;set;}
CreationTimeUtc Property datetime CreationTimeUtc {get;set;}
Directory Property System.IO.DirectoryInfo Directory {get;}
DirectoryName Property string DirectoryName {get;}
Exists Property bool Exists {get;}
Extension Property string Extension {get;}
FullName Property string FullName {get;}
IsReadOnly Property bool IsReadOnly {get;set;}
LastAccessTime Property datetime LastAccessTime {get;set;}
LastAccessTimeUtc Property datetime LastAccessTimeUtc {get;set;}
LastWriteTime Property datetime LastWriteTime {get;set;}
LastWriteTimeUtc Property datetime LastWriteTimeUtc {get;set;}
Length Property long Length {get;}
LinkTarget Property string LinkTarget {get;}
Name Property string Name {get;}
BaseName ScriptProperty System.Object BaseName {get=if ($this.Extension.Length -gt 0){$this.Name.Remove($this.Name.Length - $this.Extension.Length)}else{$this.Name};}
VersionInfo ScriptProperty System.Object VersionInfo {get=[System.Diagnostics.FileVersionInfo]::GetVersionInfo($this.FullName);}
Now that we know what's available we can use them for our scripts.
Like so:
#!/usr/bin/env powershell
function script:Loop {
foreach ($File in (Get-ChildItem)) {
##Some command
##another command
## a test
Write-Output -InputObject ('The current item is named {0}.' -f $file.Name)
Write-Output -InputObject ('The current items size it {0}.' -f $file.Length)
Write-Output -InputObject ('The current items hashcode is {0}.
' -f $file.GetHashCode())
}
}
Loop
Output
PS C:\Powershell\Scripts> . "c:\Powershell\Scripts\forloop.ps1"
The current item is named dchero.ps1.
The current items size it 755.
The current items hashcode is 31624384.
The current item is named findinstaller.ps1.
The current items size it 236.
The current items hashcode is 55386574.
The current item is named forloop.ps1.
The current items size it 466.
The current items hashcode is 50677768.
Finally you can get fancy and make changes to the objects or test them.
#!/usr/bin/env powershell
function script:Loop {
foreach ($File in (Get-ChildItem)) {
## command /argument 1
if ($file.Name -like "*.ps1") {
<# Action to perform if the condition is true #>
Write-Output "We've got a Powershell script here!"
Write-Output "The script is named $file.name"
}
else {
Write-Output "This ain't a Powershell script."
Write-Output "It's named $file.name"
}
## command / arugment 2
$newname = $file.BaseName + "-modifiedbyscript" + $file.Extension
Rename-Item -Path $file.FullName -NewName $newname -WhatIf
}
}
Loop
This might be a hard guess, if it has nothing to do with what you're looking for let me know and I'll delete this answer. If it has something to do with what you're looking I can understand why it could be "hard to explain".
function ThisThing {
[CmdletBinding()]
param(
[parameter(Mandatory, ParameterSetName = 'Expression')]
[scriptblock] $Expression,
[parameter(Mandatory, ParameterSetName = 'Condition')]
[scriptblock] $Condition,
[parameter(Mandatory, ValueFromPipeline, Position = 0)]
[object[]] $InputObject
)
process {
if($PSCmdlet.ParameterSetName -eq 'Expression') {
if($MyInvocation.ExpectingInput) {
return & $Expression
}
$InputObject | & {
process {
& $Expression
}
}
}
else {
if($MyInvocation.ExpectingInput) {
return & { if(& $Condition) { $_ } }
}
$InputObject | & {
process {
if(& $Condition) { $_ }
}
}
}
}
}
A few examples to test the function:
ThisThing (0..10) -Expression {
[pscustomobject]#{
Input = $_
}
}
Get-ChildItem | ThisThing -Expression {
if($_.CreationTime -lt (Get-Date).AddDays(-30)) {
[pscustomobject]#{
CreationDate = $_.CreationTime
Path = $_.FullName
}
}
}
Get-Process | ThisThing -Condition { $_.Name -eq 'svchost' }
ThisThing (Get-Service) -Condition { $_.Status -eq 'Stopped' }
I've found several examples showing how to get, set, and create registry keys as well as their properties and values. I need to find what property type a registry key property has (DWord, String, Multistring etc.)
If I do Get-ItemProperty I can fetch the values of a property, like so:
PS C:\> Get-ItemProperty "HKLM:\SOFTWARE\MySoftware\MyKey\" -Name MyProperty
MyProperty : MyValue
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\MySoftware\MyKey\
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\MySoftware
PSChildName : MyValue
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry
However, this does not tell me if the value of MyProperty is a string, a DWord, a multistring, or whatever else it might be. The only way I've found to get that information is to pipe it to Get-Member, because the Definition tells me what type it is:
PS C:\> Get-ItemProperty "HKLM:\SOFTWARE\MySoftware\MyKey\" -Name MyProperty | Get-Member
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
PSChildName NoteProperty string PSChildName=MyKey
PSDrive NoteProperty PSDriveInfo PSDrive=HKLM
PSParentPath NoteProperty string PSParentPath=Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\MySoftware
PSPath NoteProperty string PSPath=Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\MySoftware\MyKey\
PSProvider NoteProperty ProviderInfo PSProvider=Microsoft.PowerShell.Core\Registry
MyProperty NoteProperty string MyProperty=MyValue
I can then select the property value, split the string and fetch the first object, like so:
PS C:\> ((Get-ItemProperty "HKLM:\SOFTWARE\MySoftware\MyKey\" -Name MyProperty | Get-Member | Where-Object{$_.Name -eq "MyProperty"}).Definition -split " ")[0]
string
I verified that this gives int when I do this with a DWord attribute, but this just doesn't feel like the proper way of finding this information. Is there another, more proper or robust way to find what property type a specific registry key property has?
EDIT: I also just verified that both String and ExpandString return "string" here so there are edge cases where even the above solution does not work.
You can get the type of a property using the .GetType() method:
$value = (Get-ItemProperty 'HKLM:\SOFTWARE\MySoftware\MyKey' -Name MyProperty).MyProperty
$value.GetType().Name # outputs e. g. "String"
To explicitly test for a given type, use the -is operator:
$value -is [string] # outputs True if $value is a string
To see how each registry value type maps to a PowerShell type, I've created a registry key that contains values of all possible types and ran the following script:
$props = Get-ItemProperty "HKCU:\test"
$props.PSObject.Properties.Where{ -not $_.Name.StartsWith('PS') }.ForEach{
[pscustomobject]#{ 'Reg Type' = $_.Name; 'PS Type' = $_.Value.GetType() }
}
Output:
Reg Type PS Type
-------- -------
REG_DWORD System.Int32
REG_SZ System.String
REG_QWORD System.Int64
REG_BINARY System.Byte[]
REG_MULTI_SZ System.String[]
REG_EXPAND_SZ System.String
As you can see both REG_SZ and REG_EXPAND_SZ map to System.String, so you can't differentiate these when you read them using Get-ItemProperty. Instead you would have to use the .NET method RegistryKey.GetValueKind():
$key = Get-Item 'HKLM:\SOFTWARE\MySoftware\MyKey' # $key is of type RegistryKey
$key.GetValue('MyProperty') # Output the value
$key.GetValueKind('MyProperty') # Output the registry type of the value
This outputs "ExpandString" for registry type REG_EXPAND_SZ. For other possible values see RegistryValueKind enum.
In the context of emulating the tree command, looking at files in sub-directories:
posh>
posh> $dir = "/home/nicholas/Calibre Library/Microsoft Office User/549 (1476)"
posh>
posh> Get-ChildItem -Path $dir –File
Directory: /home/nicholas/Calibre Library/Microsoft Office User/549 (1476)
Mode LastWriteTime Length Name
---- ------------- ------ ----
----- 2/20/2021 3:22 AM 159883 549 - Microsoft Office User.txt
----- 2/20/2021 2:13 AM 351719 cover.jpg
----- 2/20/2021 2:31 AM 1126 metadata.opf
posh>
How is the filename above assigned to a variable?
An array or list of String would be sufficient. But how is the "Name" above captured? Or, for that matter, the "Directory" or other attributes?
Most cmdlets in powershell return objects. Objects have properties. When you do Get-ChildItem that returns a collection of DirectoryInfo and FileInfo objects each with their own set of properties, albeit very similar.
The following command will retrieve all the files in the path $dir as FileInfo objects and will add them to an array contained in $files
$files = Get-ChildItem -Path $dir –File
Now, each object in $files is a FileInfo object which contain properties like Name, BaseName, Directory, LastWriteTime, CreationTime, Extension, and many more. To see what properties exist on an object you can pipe | the object to Get-Member
$files | Get-Member
This will provide some information about the type of object and its properties and methods. The following list has been truncated for brevity. You may and should try this on your own.
TypeName: System.IO.FileInfo
Name MemberType Definition
---- ---------- ----------
AppendText Method System.IO.StreamWriter AppendText()
CopyTo Method System.IO.FileInfo CopyTo(string destFileName), System.IO.FileInfo CopyTo(string destFileName, ...
Create Method System.IO.FileStream Create()
CreateObjRef Method System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
CreationTime Property datetime CreationTime {get;set;}
CreationTimeUtc Property datetime CreationTimeUtc {get;set;}
Directory Property System.IO.DirectoryInfo Directory {get;}
DirectoryName Property string DirectoryName {get;}
Exists Property bool Exists {get;}
Extension Property string Extension {get;}
FullName Property string FullName {get;}
Length Property long Length {get;}
Name Property string Name {get;}
Now that you know what properties exist on the objects you may access them like so
PS C:\temp> $files.Name
test.ps1
test.xml
test1.xlsx
test2.csv
testemail.csv
testout.xml
testxml.xml
write.xml
or
PS C:\temp> $files.FullName
C:\temp\test.ps1
C:\temp\test.xml
C:\temp\test1.xlsx
C:\temp\test2.csv
C:\temp\testemail.csv
C:\temp\testout.xml
C:\temp\testxml.xml
C:\temp\write.xml
You can also pipe the objects to Select-Object to get modified objects back with only the properties you want or even custom (calculated properties).
$files | Select-Object Name, CreationTime, #{Label='Age'; Expression= {((Get-Date).Date - ($_.CreationTime).Date).Days}}
Name CreationTime Age
---- ------------ ---
test.ps1 19.02.2021 10:56:25 3
test.xml 14.02.2021 19:28:19 8
test1.xlsx 04.02.2021 19:31:54 18
test2.csv 04.02.2021 23:00:46 18
testemail.csv 03.02.2021 15:35:43 19
testout.xml 14.02.2021 19:32:03 8
testxml.xml 14.02.2021 19:33:41 8
write.xml 08.02.2021 17:26:40 14
Now that is only a small intro to Powershell. There is really much much more to it. I've seen your other posts and see that you are interested. There are many really good tutorials out there. I recommend that you have a look at a few of them and see all that there really is to learn. For starters, have a look at this one about objects in powershell
I am searching through a directory looking for files that only contain the number 0.
I have used the code:
$fileList = Select-String -Path c:\cnt\*.txt -pattern "0"
And it outputs a list, such as:
C:\cnt\itm123.txt:1:0
c:\cnt\itm1234.txt:1:0
c:\cnt\itm123456.txt:1:0
I need to only extract the itm### but the total numbers in the filename can range from 3-6.
Using the example above I need a list that looks like:
itm123
itm1234
itm123456
Once I get that I will run each through a SQL query, but I am not worried about that part.
The Select-String cmdlet returns a list of Microsoft.PowerShell.Commands.MatchInfo objects. If you call the Get-Member cmdlet with the parameter -MemberType Properties you will get a list of all exposed properties:
Name MemberType Definition
---- ---------- ----------
Context Property Microsoft.PowerShell.Commands.MatchInfoContext Context {get;set;}
Filename Property string Filename {get;}
IgnoreCase Property bool IgnoreCase {get;set;}
Line Property string Line {get;set;}
LineNumber Property int LineNumber {get;set;}
Matches Property System.Text.RegularExpressions.Match[] Matches {get;set;}
Path Property string Path {get;set;}
Pattern Property string Pattern {get;set;}
In this case, you are looking for the Filename property. So if you execute
Select-String -Path c:\cnt\*.txt -pattern "0" | select FileName
you will get all filenames:
itm123.txt
itm1234.txt
itm123456.txt
Now all you have to do is to remove the file extension which can be done using the .NET method GetFileNameWithoutExtension:
Select-String -Path c:\cnt\*.txt -pattern "0" |
ForEach-Object { [io.path]::GetFileNameWithoutExtension($_.FileName)}
Output:
itm123
itm1234
itm123456
I'm dabbling in PowerShell again. My experience thus far has been limited to file operations typically found in old school DOS batch files (build folder structures, copy files, append to a file, etc.).
The following outputs a property label ("TotalSeconds") and its property value ("12.3456").
Measure-Command { c:\_foo\test.txt c:\_bar } | select TotalSeconds
How do I output only the property value ("12.3456")?
Thanks!
The Select-Object cmdlet has an -ExpandProperty parameter that will return the property's value:
Measure-Command { c:\_foo\test.txt c:\_bar } |
Select-Object -ExpandProperty TotalSeconds
Wrap the whole command in parentheses, and use the dot operator to access the property name.
Give this a shot:
(Measure-Command { c:\_foo\test.txt c:\_bar } | select TotalSeconds).TotalSeconds;
Or even more simply:
(Measure-Command { c:\_foo\test.txt c:\_bar }).TotalSeconds;
You can use the -ExpandProperty option on the Select-Object cmdlet. Here's a real-life example.
I want to add cl.exe from the MSVC toolchain to my path, so I can compile C programs from the Windows CMD prompt and PowerShell. I never remember the full path because it's buried in the application folder.
I do remember the path to the application folder though, so I assign it to a variable and use Get-ChildItem and Select-Object with a few options you may not have seen yet*.
_
Here is the example:
<0.o> $d="c:\program files (x86)\microsoft visual studio"
<0.o> get-childitem -path $d -filter 'cl.exe' -force -recurse -erroraction silentlycontinue|select -expandproperty directory|select -expandproperty fullname
C:\program files (x86)\microsoft visual studio\2019\Community\SDK\ScopeCppSDK\vc15\VC\bin
C:\program files (x86)\microsoft visual studio\2019\Community\VC\Tools\MSVC\14.29.30037\bin\Hostx64\x64
C:\program files (x86)\microsoft visual studio\2019\Community\VC\Tools\MSVC\14.29.30037\bin\Hostx64\x86
C:\program files (x86)\microsoft visual studio\2019\Community\VC\Tools\MSVC\14.29.30037\bin\Hostx86\x64
C:\program files (x86)\microsoft visual studio\2019\Community\VC\Tools\MSVC\14.29.30037\bin\Hostx86\x86
This task confused me at first, because I tried to go straight from this:
> get-childitem -path $y -filter "cl.exe" -recurse -erroraction silentlycontinue -force|select directory
Directory : C:\program files (x86)\microsoft visual
studio\2019\Community\SDK\ScopeCppSDK\vc15\VC\bin
Directory : C:\program files (x86)\microsoft visual
studio\2019\Community\VC\Tools\MSVC\14.29.30037\bin\Hostx64\x64
Directory : C:\program files (x86)\microsoft visual
studio\2019\Community\VC\Tools\MSVC\14.29.30037\bin\Hostx64\x86
Directory : C:\program files (x86)\microsoft visual
studio\2019\Community\VC\Tools\MSVC\14.29.30037\bin\Hostx86\x64
Directory : C:\program files (x86)\microsoft visual
studio\2019\Community\VC\Tools\MSVC\14.29.30037\bin\Hostx86\x86
to this:
> get-childitem -path $y -filter "cl.exe" -recurse -erroraction silentlycontinue -force|select -expandtab directory
Name : bin
CreationTime : 7/26/2021 12:47:05 AM
LastWriteTime : 7/26/2021 12:47:06 AM
LastAccessTime : 9/29/2021 9:32:55 AM
Mode : d-----
LinkType :
Target : {}
Name : x64
CreationTime : 7/26/2021 12:49:02 AM
LastWriteTime : 7/26/2021 12:50:09 AM
LastAccessTime : 9/29/2021 9:32:55 AM
Mode : d-----
LinkType :
Target : {}
Name : x86
CreationTime : 7/26/2021 12:49:02 AM
LastWriteTime : 7/26/2021 12:50:09 AM
LastAccessTime : 9/29/2021 9:32:55 AM
Mode : d-----
LinkType :
Target : {}
Name : x64
CreationTime : 7/26/2021 12:48:55 AM
LastWriteTime : 7/26/2021 12:50:09 AM
LastAccessTime : 9/29/2021 9:32:55 AM
Mode : d-----
LinkType :
Target : {}
Name : x86
CreationTime : 7/26/2021 12:48:54 AM
LastWriteTime : 7/26/2021 12:50:09 AM
LastAccessTime : 9/29/2021 9:32:55 AM
Mode : d-----
LinkType :
Target : {}
Yikes! Adding -ExpandTab to my previous command didn't give me what I wanted. Let's back up and use the Get-Module command (gm) to look at the objects in my pipeline:
> get-childitem -path $y -filter "cl.exe" -recurse -erroraction silentlycontinue -force|select -expandproperty directory|gm
TypeName: System.IO.DirectoryInfo
Name MemberType Definition
---- ---------- ----------
LinkType CodeProperty System.String LinkType{get=GetLinkType;}
Mode CodeProperty System.String Mode{get=Mode;}
Target CodeProperty System.Collections.Generic.IEnumerable`1[[...
Create Method void Create(), void Create(System.Security...
CreateObjRef Method System.Runtime.Remoting.ObjRef CreateObjRe...
CreateSubdirectory Method System.IO.DirectoryInfo CreateSubdirectory...
Delete Method void Delete(), void Delete(bool recursive)
EnumerateDirectories Method System.Collections.Generic.IEnumerable[Sys...
EnumerateFiles Method System.Collections.Generic.IEnumerable[Sys...
EnumerateFileSystemInfos Method System.Collections.Generic.IEnumerable[Sys...
Equals Method bool Equals(System.Object obj)
GetAccessControl Method System.Security.AccessControl.DirectorySec...
GetDirectories Method System.IO.DirectoryInfo[] GetDirectories()...
GetFiles Method System.IO.FileInfo[] GetFiles(string searc...
GetFileSystemInfos Method System.IO.FileSystemInfo[] GetFileSystemIn...
GetHashCode Method int GetHashCode()
GetLifetimeService Method System.Object GetLifetimeService()
GetObjectData Method void GetObjectData(System.Runtime.Serializ...
GetType Method type GetType()
InitializeLifetimeService Method System.Object InitializeLifetimeService()
MoveTo Method void MoveTo(string destDirName)
Refresh Method void Refresh()
SetAccessControl Method void SetAccessControl(System.Security.Acce...
ToString Method string ToString()
Attributes Property System.IO.FileAttributes Attributes {get;s...
CreationTime Property datetime CreationTime {get;set;}
CreationTimeUtc Property datetime CreationTimeUtc {get;set;}
Exists Property bool Exists {get;}
Extension Property string Extension {get;}
FullName Property string FullName {get;}
LastAccessTime Property datetime LastAccessTime {get;set;}
LastAccessTimeUtc Property datetime LastAccessTimeUtc {get;set;}
LastWriteTime Property datetime LastWriteTime {get;set;}
LastWriteTimeUtc Property datetime LastWriteTimeUtc {get;set;}
Name Property string Name {get;}
Parent Property System.IO.DirectoryInfo Parent {get;}
Root Property System.IO.DirectoryInfo Root {get;}
BaseName ScriptProperty System.Object BaseName {get=$this.Name;}
I spy a FullName property that looks promising, bringing me to the final version I showed up top.
_
*For options you have not seen, pop open a powershell and type:
get-help get-childitem -showwindow
or
get-help select-object -showwindow
and use the search bar at the top of the window that appears to search for the option you want.