Why automation framework require proxy function for every powershell cmdlet? - powershell

In my new project team, for each powershell cmdlet they have written proxy function. When i asked the reason for this practice, they said that it is a normal way that automation framework would be written. They also said that If powershell cmdlet is changed then we do not need to worry ,we can just change one function.
I never saw powershell cmdlets functionality or names changed.
For example, In SQL powershell module they previously used snapin then they changed to module. but still the cmdlets are same. No change in cmdlet signature. May be extra arguments would have added.
Because of this proxy functions , even small tasks taking long time. Is their fear baseless or correct? Is there any incident where powershell cmdlets name or parameter changed?

I guess they want to be extra safe. Powershell would have breaking changes here and here sometimes but I doubt that what your team is doing would be impacted by those (given the rare nature of these events). For instance my several years old scripts continue to function properly up to present day (and they were mostly developed against PS 2-3).
I would say that this is overengineering, but I cant really blame them for that.

4c74356b41 makes some good points, but I wonder if there's a simpler approach.
Bear with me while I restate the situation, just to ensure I understand it.
My understanding of the issue is that usage of a certain cmdlet may be strewn about the code base of your automation framework.
One day, in a new release of PowerShell or that module, the implementation changes; could be internal only, could be parameters (signature) or even cmdlet name that changes.
The problem then, is you would have to change the implementation all throughout your code.
So with proxy functions, you don't prevent this issue; a breaking change will break your framework, but the idea is that fixing it would be simpler because you can fix up your own proxy function implementation, in one place, and then all of the code will be fixed.
Other Options
Because of the way command discovery works in PowerShell, you can override existing commands by defining functions or aliases with the same name.
So for example let's say that Get-Service had a breaking change and you used it all over (no proxy functions).
Instead of changing all your code, you can define your own Get-Service function, and the code will use that instead. It's basically the same thing you're doing now, except you don't have to implement hundreds of "empty" proxy functions.
For better naming, you can name your function Get-FrameworkService (or something) and then just define an alias for Get-Service to Get-FrameworkService. It's a bit easier to test that way.
One disadvantage with this is that reading the code could be unclear, because when you see Get-Service somewhere it's not immediately obvious that it could have been overwritten, which makes it a bit less straightforward if you really wanted to call the current original version.
For that, I recommend importing all of the modules you'll be using with -Prefix and then making all (potentially) overridable calls use the prefix, so there's a clear demarcation.
This even works with a lot of the "built-in" commands, so you could re-import the module with a prefix:
Import-Module Microsoft.PowerShell.Utility -Prefix Overridable -Force
TL;DR
So the short answer:
avoid making lots and lots of pass-thru proxy functions
import all modules with prefix
when needed create a new function to override functionality of another
then add an alias for prefixed_name -> override_function
Import-Module Microsoft.PowerShell.Utility -Prefix Overridable -Force
Compare-OverridableObject $a $b
No need for a proxy here; later when you want to override it:
function Compare-CanonicalObject { <# Stuff #> }
New-Alias Compare-OverridableObject Compare-CanonicalObject
Anywhere in the code that you see a direct call like:
Compare-Object $c $d
Then you know: either this intentionally calls the current implementation of that command (which in other places could be overridden), or this command should never be overridden.
Advantages:
Clarity: looking at the code tells you whether an override could exist.
Testability: writing tests is clearer and easier for overridden commands because they have their own unique name
Discoverability: all overridden commands can be discovered by searching for aliases with the right name pattern i.e. Get-Alias *-Overridable*
Much less code
All overrides and their aliases can be packaged into modules

Related

Auto-Loading Cmdlets as part of a Module dynamically

So, I was thinking: I have a lot (!) of custom cmdlets, but I don't want them to load all of them in my profile, because, naturally, that will take a lot of time. (I work fast, so my tools need to be fast.) But also, I don't want to always load them manually, because, well that's annoying. (And again ... I work fast!)
Luckily, there's a neat functionality called "Auto-Loading" which will totally solve my problem ... kind of. I just have to put my cmdlets into script modules, and they will be loaded automatically, right?
It seems though, there need to be some requirements met, for PowerShell to "detect" the cmdlets belonging to a module. The easiest way I know so far, is to simply put each cmdlet in a single ps1 file and then create a manifest somewhat like this:
#{
ModuleVersion = "1.0"
NestedModules = #(
"Get-Something.ps1",
"Remove-Something.ps1",
"Test-Something.ps1",
"Run-Something.ps1",
"Invoke-Something.ps1",
"Start-Something.ps1",
"Stop-Something.ps1"
),
CmdletsToExport = #(
"Get-Something",
"Remove-Something",
"Test-Something",
"Run-Something",
"Invoke-Something",
"Start-Something",
"Stop-Something"
)
}
This does work. But as I said .. I work fast, and I'm lazy. It would be much easier, if the module could discover its members dynamically. Basically, all ps1 files in the same folder. This would work pretty easily with some code in the psm1 file. But then, the Auto-Loading would not work. I understand PowerShell does need to know the exported cmdlets before hand.
Is there any other, dynamic way to do that, other than specifying them explicitly in the psd1 module manifest?
If you are worried about the size or organization of your module, create different modules for the different common subjects of your cmdlets, and put your functions into the psm1 for each module. Then make sure you add the cmdlet definitions to CmdletsToExport in the module manifest (psd1). If you have a centralized feed, distributing your modules becomes easy, and if you simply want all of them at a given time you can create a top level module that lists your other modules as dependencies so they get installed when you install the top level module. Unless you have hundreds to thousands of cmdlets to export as part of a single module, you shouldn't need to worry about performance problems.
For each module you have on the PSModulePath, you can use wildcards in the CmdletsToExport and FunctionsToExport sections of your manifest for auto-complete to work, if you don't want to export them name by name.
Edit: Technically, you can call Set-PSReadLineKeyHandler and Register-ArgumentCompleter yourself, however, you have the same problem now in that you need to add this to the $profile for each user or machine where you want the autocomplete to work, in addition to shipping your code there in the first place. So it doesn't really solve your issue.

Using dot source function in params

I'm going to start by saying I'm still pretty much a rookie at PowerShell and hoping there is a way to do this.
We have a utils.ps1 script that contains just functions that we dot source with in other scripts. One of the functions returns back a default value if a value is not passed in. I know I could check $args and such but what I wanted was to use the function for the default value in the parameters.
param(
[string]$dbServer=$(Get-DefaultParam "dbServer"),
[string]$appServer=$(Get-DefaultParam "appServer")
)
This doesn't work since the Util script hasn't been sourced yet. I can't put the dot source first because then params doesn't work as it's not the top line. The utils isn't a module and I can't use the #require.
What I got working was this
param(
[ValidateScript({ return $false; })]
[bool]$loadScript=$(. ./Utils.ps1; $true),
[string]$dbServer=$(Get-DefaultParam "dbServer"),
[string]$appServer=$(Get-DefaultParam "appServer")
)
Create a parameter that loads the script and prevent passing a value into that parameter. This will load the script in the correct scope, if I load it in the ValidateScript it's not in the correct scope. Then the rest of the parameters have access to the functions in the Utils.ps1. This probably is not a supported side effect, aka hack, as if I move the loadScript below the other parameters fail since the script hasn't been loaded.
PowerShell guarantee parameters will always load sequential?
Instead should we put all the functions in Utils.ps1 in global scope? this would need to run Utils.ps1 before the other scripts - which seems ok in scripting but less than ideal when running the scripts by hand
Is there a more supported way of doing this besides modules and #require?
Better to not use default value of params and just code all the checks after sourcing and check $args if we need to run the function?
It would be beneficial to instead turn that script into a PowerShell Module, despite your statement that you desire to avoid one. This way, your functions are always available for use as long as the module is installed. Also, despite not wanting to use it, the #Require directive is how you put execution constraints on your script, such as PowerShell version or modules that must be installed for the script to function.
If you really don't want to put this into a module, you can dot-source utils.ps1 from the executing user's $profile. As long as you don't run powershell.exe with the -NoProfile parameter, the profile loads with each session and your functions will be available for use.

How to share data between cmdlets in a module?

I'm currently working on a module in PowerShell which uses a standard REST API in the background. For that, I wrote a Connect-Server cmdlet that retrieves an auth key for later calls.
My question is: Is there any best practice regarding sharing the data with other cmdlets? I know I could easily just return it from the Connect function and pass it to the following cmdlet, but that's not what I'm looking for.
Until now, I've been using global variables for that exchange of data. But as I've read in some best practice guidelines you should try not to pollute the global scope.
Other solutions I've seen use Get and Set cmdlets, but I don't think that's the best PowerShell way of doing it.
So are there any other ways of solving that?
The normal way is to return data from one cmdlet and store it either in a variable or forward it to the pipeline. Another way of sharing data might be serializing (ConverTo-Json, ConvertTo-Csv, ...) it to a file (located in e.g. $env:TEMP, or created via New-Temporaryfile), and deserializing it back again in another cmdlet (at cost of DISK I/O). Personally I'm always the result in a variable for lather usage and inject it in the next cmdlet (or use the pipeline).
Using global variables is not the best idea since you don't know on which parameters your cmdlet/function depends on.
So as the guys at PoshCode stated, the best way to do such a thing is using a variable in script scope as this is available for all cmdlets in the module but not visible for users.

Powershell naming conventions/scoping

I want to create a Powershell function/cmdlet which installs (and one that uninstalls) a web application: copies files, creates an app pool, creates the web application, sets up all kinds of IIS properties, does some web.config modifications, etc. I'm confused about how I should name it. Powershell has this verb-object naming convention, and it's all nice, but the names I want to use (New-WebApplication, etc.) are already taken by the WebAdministration module (which this new function will use internally). Is there a nice way to scope my functions to make it clear that it's a different module? Like mymodule.New-WebApplication, My-New-WebApplication, New-MyWebApplication? Or I could call it Install-WebApplication but that could lead to confusion because of reusing the same name.
I just ran into this recently for a similar issue. This could have many opinionated answers but this would handle the way to scope my functions to make it clear that it's a different module.
You could use the -Prefix parameter of Import-Module
Import-Module mymodule -Prefix Super
So when you go to use your cmdlet you would call it with
New-SuperWebApplication
Alternatively, you can also explicitly call the cmdlet with the module path
mymodule\New-WebApplication
I agree with Matt's answer, but I wanted to offer another perspective.
I wrote a module where the intention was specifically to recreate the functionality of an existing cmdlet. I named my function differently, but I also exported functions from the module that allow the caller to overrride the existing cmdlet with mine (using an Alias, which is interpreted first), and then to also undo that process.
This allowed someone to explicitly call the function without needing to use -Prefix nor use the \ syntax, using the new name with new code, but it also allowed one to use my function as a drop-in replacement for existing code by calling a single new command.
Here's that module if you want to take a look:
DnsCmdletFixes

Does powershell have a method_missing()?

I have been playing around with the dynamic abilities of powershell and I was wondering something
Is there is anything in powershell analogous to Ruby's method_missing() where you can set up a 'catch all method' to dynamically handle calls to non-existant methods on your objects?
No, not really. I suspect that the next version of PowerShell will become more in line with the dynamic dispatch capabilities added to .NET 4 but for the time being, this would not be possible in pure PowerShell.
Although I do recall that there is a component model similar to that found in .NET's TypeDescriptor for creating objects that provide properties and methods dynamically to PowerShell. This is how XML elements are able to be treated like objects, for example. But this is poorly documented if at all and in my experience, a lot of the types/methods needed to integrate are marked as internal.
You can emulate it, but it's tricky. The technique is described in Lee Holmes book and is boiled down to two scripts - Add-RelativePathCapture http://poshcode.org/2131 and New-CommandWrapper http://poshcode.org/2197.
The essence is - you can override any cmdlet via New-CommandWrapper. Thus you can redefine Out-Default that is implicitly invoked at the end of almost every command (excluding commands with explicit formatters like Format-Table at the end). In the new Out-Default you check if the last command threw an exception saying that no method / property was found. And there you insert your method_missing logic.
You could use Try Catch within Powershell 2.0
http://blogs.technet.com/b/heyscriptingguy/archive/2010/03/11/hey-scripting-guy-march-11-2010.aspx