What's wrong with my recursive delete? - powershell

Get-ChildItem -recurse | ? {$_.Extension -eq ".obj" } | %{del $_}
~~~~~~
CategoryInfo : ObjectNotFound: (C:\Temp\compilerLimits\template.obj:String) [Remove-Item], ItemNotFoundException
FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand
Trying to recursively delete all .obj files;
instead I get this.

Try it like this:
Get-ChildItem -recurse *.obj | Remove-Item
In the case of | %{del $_}, $_ is a System.IO.FileInfo object and when PowerShell uses this for the LiteralPath parameter, it sees that the LiteralPath parameter takes a string. The conversion of FileInfo to a string seems to use the FileInfo.ToString() method which in some cases (like for subdirs) doesn't include the full path - just the filename. That will cause the error your are seeing. When you pipe in the FileInfo object, pipeline argument binding rules are used. The LiteralPath parameter has an alias called PSPath. This property is added to each FileInfo object by PowerShell's type system. You can see this with Get-ChildItem *.obj | Get-Member. And since the LiteralPath parameter has ValueFromPipelineByPropertyName set to true, PowerShell will get the argument value from the object's PSPath property.
You can read more about this in Item 8 of my Effective PowerShell ebook.

Related

How to use previous command output as a parameter in Powershell?

I need to write a Powershell one-liner to do a complex task. I need it to be strictly one line because I want to run it in a Go and Python script. The task requires taking the output of the first command and use it as a parameter in the second command.
I thought it was a simple task but I am struggling with it quite a bit. For example, the below command does not work:
$obj = Get-Item . | Get-Item $obj.Root | Format-List *
Get-Item : 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.
At line:1 char:21
+ $obj = Get-Item . | Get-Item $obj.Root | Format-List *
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (C:\Users\fhcat:PSObject) [Get-Item], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Microsoft.PowerShell.Commands.GetItemCommand
What am I doing wrong?
Strictly one line sounds like a personal preference rather than a technical requirement.
However, you can end a statement with a semicolon or process the input via ForEach-Object
$obj = Get-Item .; Get-Item $obj.Root | Format-List *
or
Get-Item . | ForEach-Object {Get-Item $_.Root} | Format-List *

I can't get Powershell StartsWith function to work

I'm trying to create a Powershell script that prints out only certain AD groups from the Folder Permission settings. However for some reason Powershell doesn't recognize StartsWith function.
("C:\folder" | get-acl).Access | ForEach-Object { if (($_.IdentityReference).StartsWith("sl_test")) { continue }; $_ }
When I run this I got errors similar to this for every foreach object:
Method invocation failed because [System.Security.Principal.NTAccount] does not contain a method named 'StartsWith'.
At C:\temp\test.ps1:1 char:56
+ ("C:\folder" | get-acl).Access | ForEach-Object { if (($_.IdentityReference).St ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
Any suggestions on how to get this to work?
IdentityReference is a [System.Security.Principal.NTAccount]according to your error message.
But .StartWith is a method on the String type. If you call a method, Powershell does no magic for you, AFAIK.
Try ... ($_.IdentityReference) -match "^sl_test" ..., which should do the implicit string conversion.
If you want the string representation of an IdentityReference (regardless of whether it's and NTAccount object or a SID), you can reference the Value property:
$_.IdentityReference.Value.StartsWith('sl_test')
Try:
Get-Acl -Path "C:\folder" | Select-Object -ExpandProperty Access | Where-Object {$_.IdentityReference -like "sl_test*" }
You can customize the output with an additional | Select-Object -Property XY

ErrorVariable output not as expected

First, here is my code:
$InputPath = "\\Really\long\path\etc"
Get-ChildItem -Path $InputPath -Recurse -Force -ErrorVariable ErrorPath
$ErrorPath | select TargetObject
The problem I am facing is with the ErrorVariable parameter. Paths that are too long for Get-ChildItem to process are stored there (typically around 250+ characters). When I pipe $ErrorPath to select, the output looks like this:
TargetObject : \\Really\long\path\etc\1
TargetObject : \\Really\long\path\etc\2
TargetObject : \\Really\long\path\etc\3
However, if I run that last line again (either by running selection or by manually typing it in), the output changes to this:
TargetObject
------------
\\Really\long\path\etc\1
\\Really\long\path\etc\2
\\Really\long\path\etc\3
I am at a loss as to how to explain this. I prefer the second output, but I don't know why it is different from the first to the second time. Any ideas?
When there are multiple types of objects in a pipeline the first object type determines the formatting for the rest of the objects. If you ran your commands individually on the command prompt you wouldn't see this. Each line starts a new pipeline. However when you run these commands in a script, the whole script is executed as a pipeline. To work around this use Out-Default e.g.:
$InputPath = "\\Really\long\path\etc"
Get-ChildItem $InputPath -Recurse -Force -ErrorVariable ErrorPath
$ErrorPath | select TargetObject | Out-Default
The former output is in list format, the latter in table format. You can enforce one or the other by piping your data into the Format-List or Format-Table cmdlet respectively:
PS C:\> $ErrorPath | select TargetObject | Format-List
TargetObject : \\Really\long\path\etc\1
TargetObject : \\Really\long\path\etc\2
TargetObject : \\Really\long\path\etc\3
PS C:\> $ErrorPath | select TargetObject | Format-Table
TargetObject
------------
\\Really\long\path\etc\1
\\Really\long\path\etc\2
\\Really\long\path\etc\3

Why don't errors get returned when calling properties that don't exist?

Given the following snippet
$drives = Get-PSDrive
foreach($drive in $drives)
{
Write-Host $drive.Name "`t" $drive.Root
Write-Host " - " $drive.Free "`t" $drive.PropertyDoesntExist
}
The drive.PropertyDoesntExist property doesn't... erm... exist so I would expect an error to be thrown but instead it returns a null.
How can I get errors or exceptions?
EDIT - Me bad - I asked 2 questions in one so I moved one into a separate question.
The NextHop Blog provides a good solution to this problem. It doesn't give you an error, but instead a boolean. You can use Get-Member to get a collection of all of the real properties of the object's type and then match for your desired property.
Here's an example for strings:
PS C:\> $test = "I'm a string."
PS C:\> ($test | Get-Member | Select-Object -ExpandProperty Name) -contains "Trim"
True
PS C:\> ($test | Get-Member | Select-Object -ExpandProperty Name) -contains "Pigs"
False
If you explicitly want an error, you may want to look into Set-Strictmode as Set-StrictMode -version 2 to trap non-existent properties. You can easily turn it off when you're done with it, too:
PS C:\> Set-StrictMode -version 2
PS C:\> "test".Pigs
Property 'Pigs' cannot be found on this object. Make sure that it exists.
At line:1 char:8
+ "test". <<<< Pigs
+ CategoryInfo : InvalidOperation: (.:OperatorToken) [], RuntimeException
+ FullyQualifiedErrorId : PropertyNotFoundStrict
PS C:\> Set-StrictMode -off

Error retrieving registry value with Powershell

I'm attempting to read a value from a registry entry with Powershell. This is fairly simple, however, one particular registry key is giving me trouble.
If I run the following, I can't get the value of the (default) of "$setting".
C:\Program Files\PowerGUI> $setting = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\IniFileMapping\Autorun.inf"
C:\Program Files\PowerGUI> $setting
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\
CurrentVersion\IniFileMapping\Autorun.inf
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\
CurrentVersion\IniFileMapping
PSChildName : Autorun.inf
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry
(default) : #SYS:DoesNotExist
Normally, I would do $setting.Attribute, or $setting.(default). However, that results in the following error:
C:\Program Files\PowerGUI> $setting.(default)
The term 'default' is not recognized as a cmdlet, function, operable program, or script file. Verify the term and try again.
At :line:1 char:17
+ $setting.(default <<<< )
How do I get the value of the "(default)" attribute?
Thanks in advance.
EDIT Had to look through and old script to figure this out.
The trick is that you need to look inside the underlying PSObject to get the values. In particular look at the properties bag
$a = get-itemproperty -path "HKLM:\Some\Path"
$default = $a.psobject.Properties | ?{ $_.Name -eq "(default)" }
You can also just use an indexer instead of doing the filter trick
$default = $a.psobject.Properties["(default)"].Value;
Another version:
(Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\IniFileMapping\Autorun.inf').'(default)'
Use Get-Item to get an object representing the registry key:
PS > $regKey = Get-Item HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\IniFileMapping\Autorun.inf
This gives you an instance of RegistryKey. RegistryKey has a method named GetValue; if the argument to GetValue is the empty string (''), then it will return the (default) value:
PS > $regKey.GetValue('')
Why is this better than Get-ItemProperty? It extends more naturally to Get-ChildItem. Get-ChildItem will give you a list of RegistryKey objects. In my particular case, I wanted to list the install paths of the versions of Python installed on my machine:
PS C:\> get-childitem HKLM:\SOFTWARE\Wow6432Node\Python\PythonCore\*\InstallPath `
>> | foreach-object { $_.GetValue('') }
C:\Python26\ArcGIS10.0\
C:\Python\27\