I'm implementing a custom PowerShell provider. I'm now working on the remove-item cmdlet implementation.
The RemoveItem method has the following signature:
protected override void RemoveItem(string path, bool recurse)
When I type: Remove-Item .\Myobject -recurse the PowerShell infrastructure provides me with the value true in the recurse parameter of the RemoveItem method.
However when I type: Remove-Item .\MyObject' I get a question:
The item at MyObject has children and the Recurse parameter was not specified. If you continue,
all children will be removed with the item. Are you sure you want to continue?
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):
I guess this question is presented to my by the PowerShell infrastructure. This is perfectly fine because the object that I want to remove is a container. If I answer yes to the above question the PowerShell infrastructure does not set the recurse parameter. In fact it is false when my RemoveItem method is called. I would except the parameter to be true because I answered yes to the question.
My questions are:
Why does PowerShell not set the bool recurse parameter to the correct value?
Do I need to get the value (answer to the question) in some other way? How?
If above is not possible is there a way to suppress the question?
Ultimately if you're asked to remove a container then it is inherently recursive unless the container doesn't contain other containers. I believe PowerShell prompts because the action affects more than the user might initially be aware of (all the container's contents) and warrants confirmation. So in this case, I think the -recurse is used to tell PowerShell "I know what I'm doing".
Where -recurse makes more sense is if you were to execute something like this:
Remove-Item *.bak -recurse
In this case, you want to recursively search for files to delete. Unfortunately the Recurse parameter on Remove-Item is broken for this usage - from the docs:
Because the Recurse parameter in this
cmdlet is faulty, the command uses
the Get-Childitem cmdlet to get the
desired files, and it uses the
pipeline operator to pass them to the
Remove-Item cmdlet.
So the way to do this currently is:
Get-ChildItem . -r *.bak | Remove-Item
You can suppress the question by setting $ConfirmPreference="None"
http://blogs.msdn.com/b/powershell/archive/2006/12/15/confirmpreference.aspx
Related
Is there a way to manually clear the pester TestDrive, other than something like Remove-Item "TestDrive:\" -Recurse -Force
AFAIK there isn't a function to trigger a clear of TestDrive, so yes I would recommend using something like Remove-Item $TestDrive -Recurse -Force if you had a specific need to.
However you should also be aware that a TestDrive is scoped to a Describe or Context block and automatically cleaned up at the end of those blocks. So if you want to avoid conflicts between different usages of TestDrive just have those test in different Context blocks.
I've never used powershell or any cmd line to try and rename files, nor so I really know much about script writing in general.
I've already had some success in renaming the files in question but am stuck on the last piece of the puzzle.
Original file names:
NEE100_N-20210812_082245.jpg
NEE101_E-20210812_083782.jpg
NEE102_W-20210812_084983.jpg
I successfully change those to AT-###-N-......jpg using:
Rename-Item -NewName {$_.name -replace "NEE\d\d\d_", "AT-112-"}
And this is what they looked like after:
AT-112-N-20210812_082245.jpg
AT-112-E-20210812_083782.jpg
AT-112-W-20210812_084983.jpg
Now however, I have a few files that look like this:
AT-112-NewImage-20210812_083782.jpg
AT-112-NewImage-20210812_093722.jpg
and I want to change them to:
AT-112-D1-20210812_083782.jpg
AT-112-D2-20210812_093722.jpg
...and so on.
I've tried a few things here to try and do that. Such as replacing "NewImage" with "D" and then using something like this (not exact, just an example):
$i = 1
Get-ChildItem *.jpg | %{Rename-Item $_ -NewName ('19981016_{0:D4}.jpg' -f $i++)}
But this did not work. I have seen scripts that use sequential numbering either added as a suffix or a prefix. But I can't figure out how to do this if what I want to have sequence numbering in the middle of the name.
Hopefully this make sense, if I need more elaboration, let me know. Thanks!
You need to use an expression (inside (...)) as your -replace substitution operand in order to incorporate a dynamic value, such as the sequence number in your case.
In order to use a variable that maintains state across multiple invocations of a delay-bind script block ({ ... }, the one being passed to the -NewName parameter in your first attempt), you need to create the variable in the caller's scope and explicitly reference it there:
This is necessary, because delay-bind script blocks run in a child scope, unfortunately,[1] so that any variables created inside the block go out of scope after every invocation.
Use Get-Variable to obtain a reference to a variable object in the caller's (parent) scope[2], and use its .Value property, as shown below.
$i = 1
Get-ChildItem *.jpg | Rename-Item -NewName {
$_.Name -replace '-NewImage-', ('-D{0}-' -f (Get-Variable i).Value++)
} -WhatIf
Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.
Note: The above solution is simple, but somewhat inefficient, due to the repeated Get-Variable calls - see this answer for more efficient alternatives.
[1] This contrasts with the behavior of script blocks passed to Where-Object and ForEach-Object. See GitHub issue #7157 for a discussion of this problematic discrepancy.
[2] Without a -Scope argument, if Get-Variable doesn't find a variable in the current scope, it looks for a variable in the ancestral scopes, starting with the parent scope - which in this case the caller's. You can make the call's intent more explicitly with -Scope 1, which starts the lookup from the parent scope.
Let's say I wrote a PowerShell script that includes this commmand:
Get-ChildItem -Recurse
But instead I wrote:
Get-ChildItem -Re
To save time. After some time passed and I upgraded my PowerShell version, Microsoft decided to add a parameter to Get-ChildItem called "-Return", that for example returns True or False depending if any items are found or not.
In that virtual scenario, do I have I to edit all my former scripts to ensure that the script will function as expected? I understand Microsoft's attempt to save my typing time, but this is my concern and therefore I will probably always try to write the complete parameter name.
Unless of course you know something I don't. Thank you for your insight!
This sounds more like a rant than a question, but to answer:
In that virtual scenario, do I have I to edit all my former scripts to ensure that the script will function as expected?
Yes!
You should always use the full parameter names in scripts (or any other snippet of reusable code).
Automatic resolution of partial parameter names, aliases and other shortcuts are great for convenience when using PowerShell interactively. It lets us fire up powershell.exe and do:
ls -re *.ps1|% FullName
when we want to find the path to all scripts in the profile. Great for exploration!
But if I were to incorporate that functionality into a script I would do:
Get-ChildItem -Path $Home -Filter *.ps1 -Recurse |Select-Object -ExpandProperty FullName
not just for the reasons you mentioned, but also for consistency and readability - if a colleague of mine comes along and maybe isn't familiar with the shortcuts I'm using, he'll still be able to discern the meaning and expected output from the pipeline.
Note: There are currently three open issues on GitHub to add warning rules for this in PSScriptAnalyzer - I'm sure the project maintainers would love a hand with this :-)
Are there any official guidelines from Microsoft about when to add -Confirm, -Force, and -WhatIf parameters to custom PowerShell cmdlets? There doesn't seem to be a clear consensus about when/how to use these parameters. For example this issue.
In the absence of formal guidelines, is there a best practice or rule of thumb to use? Here is some more background, with my current (possibly flawed) understanding:
-WhatIf
The -WhatIf flag displays what the cmdlet would do without actually performing any action. This is useful for a dry run of a potentially destabilizing operation, to see what the actual results would be. The parameter is automatically added if the cmdlet's Cmdlet attribute has the SupportsShouldProcess property set to true.
It seems like (but I'd love to see more official guidance here) that you should add -WhatIf if you are ever adding or removing resources. (e.g. deleing files.) Operations that update existing resources probably wouldn't benefit from it. Right?
-Force
The -Force switch is used to declare "I know what I'm doing, and I'm sure I want to do this". For example, when copying a file (Copy-File) the -Force parameter means:
Allows the cmdlet to copy items that cannot otherwise be changed, such as copying over a read-only file or alias.
So to me it seems like (again, I'd love some official guidance here) that you should add an optional -Force parameter when you have a situation where the cmdlet would otherwise fail, but can be convinced to complete the action.
For example, if you are creating a new resource that will clobber an existing one with the same name. The default behavior of the cmdlet would report an error and fail. But if you add -Force it will continue (and overwrite the existing resource). Right?
-Confirm
The -Confirm flag gets automatically added like -WhatIf if the cmdlet has SupportsShouldProcess set to true. In a cmdlet if you call ShouldProcess then the user will be prompted to perform the action. And if the -Confirm flag is added, there will be no prompt. (i.e. the confirmation is added via the cmdlet invocation.)
So -Confirm should be available whenever a cmdlet has a big impact on the system. Just like -WhatIf this should be added whenever a resource is added or removed.
With my potentially incorrect understanding in mind, here are some of the questions I'd like a concrete answer to:
When should it be necessary to add -WhatIf/-Confirm?
When should it be necessary to add -Force?
Does it ever make sense to support both -Confirm and -Force?
I haven't researched whether the documentation is this detailed, but the following are based on my observations:
You should use -WhatIf for anything that makes a change. Updates are changes that can benefit from -WhatIf (e.g., what if you want to make a lot of updates?).
-Force means "force overwrite of an existing item" or "override a read-only file system attribute". In either case the success of the action depends on the user having permission.
-Confirm and -Force are not mutually exclusive. For example, you can confirm an action to write a file, but the file might be protected with the read-only attribute. In this case the action would fail unless you also specify -Force.
If you want to validate that your implementation of these common parameters is compliant to the guidelines (for example, Set-Xxx cmdlets should have -Confirm and -WhatIf), then you can use the excellent PsScriptAnalyzer module (which is based on code analysis).
Make sure the module is installed:
PS E:\> Install-Module -Name 'PsScriptAnalyzer'
Then run PowerShell Code Analysis as follows:
PS E:\> Invoke-ScriptAnalyzer -Path . | FL
RuleName : PSUseShouldProcessForStateChangingFunctions
Severity : Warning
Line : 78
Column : 10
Message : Function 'Update-something' has verb that could change system state.
Therefore, the function has to support 'ShouldProcess'.
Documentation (and sources) can be found on GitHub:
https://github.com/PowerShell/PSScriptAnalyzer
As an added observation, -Force should not overrule -WhatIf. Or in other words: -WhatIf has priority over -Force.
If you use:
Get-ChildItem -Recurse | Remove-Item -Recurse -Force -WhatIf
it will result in the following output:
What if: Performing the operation "Remove Directory" on target "E:\some directory\".
It will not actually remove the items, even when -Force is specified.
This means that you should never write:
if($Force -or $Pscmdlet.ShouldProcess($)) {
...
}
In most example that I see in tutorials and books, the -LiteralPath option is almost never used by default (the -Path option seems to be preferred). Because the -LiteralPath option allows to use reserved characters (e.g. []), I don't understand why it is not used more often (if not, all the time). Is it because it is preferred to escape reserved characters manually, because it has a high performance cost, because not all cmdlet support this option or because something else?
One thing to consider is that -Path is most likely to be the parameter that used when a string is passed in through the pipeline.
Looking at the Get-Item cmdlet for instance:
-LiteralPath <string[]>
Required? true
Position? Named
Accept pipeline input? true (ByPropertyName)
Parameter set name LiteralPath
Aliases PSPath
Dynamic? false
-Path <string[]>
Required? true
Position? 0
Accept pipeline input? true (ByValue, ByPropertyName)
Parameter set name Path
Aliases None
Dynamic? false
Piping a string gets you -Path. Your object(s) would have to have a LiteralPath (or PSPath) property for Get-Item to use it. If your object has both, -Path is used.
I guess this doesn't necessarily answer the question; it's a bit circular to say "-Path is used more often because it's more popular". A better question might be, "Why doesn't -Path behave like -LiteralPath in the first place?"