Powershell: Feature Request Support Versioning for Standalone Functions

Created on 23 Jan 2020  路  60Comments  路  Source: PowerShell/PowerShell

Summary of the new feature/enhancement

When using Get-Command the only time you see version information for a command is when it is part of a module with a manifest. And even then, I think the version is really for the module. There should be a way to support versioning on a per function level, especially for stand-alone files. I may have a single function command in a PS1 file. I'm happy to dot source and run the command. But I'd like to see a version number when I use Get-Command.

Proposed technical implementation details (optional)

The System.Management.Automation.FunctionInfo class has the necessary properties, but they are read-only. It would be nice to have a function equivalent to the PScriptInfo metadata that is used with PowerShellGet and published scripts. Let me have a metadata section that PowerShell will process and use when the function is imported into PowerShell.

Committee-Reviewed Issue-Enhancement Resolution-Answered

Most helpful comment

This isn't about running or distributing functions. All I'd like is a mechanism to provide a way to track and discover version information so that if I run Get-Command, or perhaps a new command like Get-Function, I can see some metadata about the function. I have some ideas that I might try to prototype through PowerShell scripting.

All 60 comments

Please add info how you'd want to enhance PowerShell language to add the version. In cmdletbinding attribute?

My thought was to do something like New-ScriptFileInfo which generates a comment header like this:

<#PSScriptInfo

.VERSION 1.0

.GUID 66c837b7-6478-4571-9dec-0621e13df4c3

.AUTHOR Jeff

.COMPANYNAME

.COPYRIGHT 2020

.TAGS

.LICENSEURI

.PROJECTURI

.ICONURI

.EXTERNALMODULEDEPENDENCIES 

.REQUIREDSCRIPTS

.EXTERNALSCRIPTDEPENDENCIES

.RELEASENOTES


.PRIVATEDATA

#>

<# 

.DESCRIPTION 
 This is a cool function 

#> 
Param()

Make it PSFunctionInfo and have Get-Command parse it. Or in the same way that PowerShell can parse #requires, have it parse #version. Where it gets tricky is when you have multiple functions in the same ps1 file, so anything I would do as a function author, would need to be enclosed in the function. This could be a parsed comment block, new cmdletbinding() option, or perhaps a whole new attribute.

Function Get-Awesomeness {

 [cmdletbinding()]
 [alias("awe")]
 [outputtype("psobject")]
 [version("1.1.0")]

 Param ()

 ...
}

I don't see a value from the feature. We can publish on PowerShellGet and distribute PowerShell scripts as only modules, we can not do this for files. Can you describe a business case/workflow?

There are many people who create PowerShell solutions that are never published in the gallery. They are used internally, say in a corporate environment. And not everything is packaged into a module. I may work in a corporate environment and have a standalone script file with a function. I want the ability to keep track of the version for that function. If I run Get-Command Get-CompanyFoo, I need to be able to see a version number. For that matter, even in a module, all exported functions share the same version number. I'd like a facility to track version information on a per-function basis. And of course, without having to rely on reading the source code. If a help desk technician is using one of my functions, they need to be able to use PowerShell to discover the command version.

Certainly, not every function needs this, even those in a module. I have no problem with a function in a module using the module version if no command-specific version is detected.

@jdhitsolutions Thanks!

I see two component in your scenario - (1) a version tracking system, (2) a distribution system.
The first one could be GIT (others?).
It is not clear how you distribute script files.

(I am trying to understand where is right place for the requested attribute.)

I leave functions at clients on a regular basis, which are current as of the last time I used them at that particular client. One I used at a client today springs to mind - getWUlog. It knows how to connect to a remote system using both WinRM and psexec (it checks to see if 139 or 5985 are open) and retrieve the WindowsUpdate log from that computer, retrieve it to the local computer, and open notepad.

Another is getFrameworkVersion. Does just that - looks to find the .NET Framework version installed on a given computer, using either CIM or the remote registry service (again - checks ports to figure out what to do), reports those, and reports on what versions are blocked, if any.

These are standalone functions. Not part of a module. But they have gone through regular updates over the last few years.

It would be great to attach a version to each of them. Right now, I just have a "last modified date" at the top of the source. Not as useful...

Distribution is irrelevant in my mind to this discussion. And yes, git plays a role but isn't the issue. If I am in PowerShell and am using a standalone function. When I run Get-Command Get-Foo I want to see the version number of the function. It is as simple as that. It doesn't matter how the function got distributed. Give me a way to inject metadata into the function.

And to be absolutely clear, I don't want to force a user, or even myself, to track down the source and look for comments or notes.

I definitely write/use some standalone functions, but most are in modules. And most of those are random flotsam tools that can be used as standalone functions. I always put semantic versions and changelogs in .NOTES within the comment block, and a [version]$Version in the begin block, because I want to keep track of what changes I made to a particular function, not just to the module (which I increment when I make changes to the inner functions). Having something like [version()] or [cmdletbinding(Version='01.00.0000')] would be spectacular; I like it better than .VERSION because I know a lot of people put multiple functions into a psm1 file (although I don't).
I use git, also, btw, but I don't commit every time I make a change to an individual function; I bundle them up and commit when I change the module, usually w the module version as the commit message. Yeah, yeah, I know, I'm weird.

If the script is found in $env:PATH, it does show up for Get-Command. So it seems reasonable to map the existing ScriptFileInfo metadata to the ExternalScriptInfo members which includes the Version.

Be careful with that assumption Steve. I might have a single ps1 file with multiple functions. I don't want to trust that Get-Command can find the script file. Heck, I might want to define a versioned function in a PowerShell profile script.

Definitely have versioned functions in my profile script.

We already have the System.Management.Automation.FunctionInfo type which has a Version property. We need a way so that when the function is loaded, the version property is populated from metadata in the function.

Thanks all for great feedback! I have still some questions. What behavior you expect if PowerShell found some functions with same name? Run function with higher version? Should we take into account scopes (you load in current scope old function - should we call more new function from global scope)? Or we want to have only informational attribute?

This isn't about running or distributing functions. All I'd like is a mechanism to provide a way to track and discover version information so that if I run Get-Command, or perhaps a new command like Get-Function, I can see some metadata about the function. I have some ideas that I might try to prototype through PowerShell scripting.

Thanks all for great feedback! I have still some questions. What behavior you expect if PowerShell found some functions with same name? Run function with higher version? Should we take into account scopes (you load in current scope old function - should we call more new function from global scope)? Or we want to have only informational attribute?

I don't think anyone has suggested any change in the way PS executes functions. We just want to put a version on them. Informational and a way to interrogate same.

Thanks! Next question I have. A file with some functions could have a version attribute common for all functions in the file. Should we consider this?

I have some ideas that I might try to prototype through PowerShell scripting.

You could share the ideas in gist.

That's my plan.

Should it be classic Version or SymVer 2.0?

I am becoming increasingly convinced function versioning is a good thing.

To answer @iSazonov latest question - if we are to have it, why NOT use SymVer?

And regarding a file of functions - I'd argue that the attribute is per function with no per-file attributes. Much like today's CMDLETBinding works.

Prototype is pulled in ##11686

You can download an artifact from the PR and play with new attribute.

Sweet.

Thinking about it - why not extend the PSVersionAttribute to include the version of the script. If we version modules and (hopefully now!) functions, why not scripts?

The PSVersionAttribute could also serve as a default value for functions defined in the script.

And before anyone else asks... Can function execution be versioned?

We could load multiple versions of a function, execute the latest by default, but have a -PSFunctionVersion common parameter that says which specific version to run.
No doubt there are more alternatives - but should we provide versioned function execution?

If it's an attribute you could always just reuse it, much like how [CmdletBinding()] can be applied to both functions and scripts, you could always just use [PSVersion()] for both functions and scripts. This is looking like an interesting approach. 馃檪

I don't foresee a lot of use of this kind of thing in modules, where we already have versioned code for the most part, but for standalone scripts and functions this could be very interesting indeed 馃檪

This isn't about running or distributing functions. All I'd like is a mechanism to provide a way to track and discover version information so that if I run Get-Command, or perhaps a new command like Get-Function, I can see some metadata about the function. I have some ideas that I might try to prototype through PowerShell scripting.

If we are to add function versioning, why limit it to purely documentation? If I can specify and query function versions, wouldn't it be sensible to allow execution based on version number?

Either a way to load multiple versions of a function and be able to pick the one to execute, or enable the user to choose amongst different versions to load into the function list we see with gcm and then execute that one.

So here's a proof of concept I worked up based on the ScriptFileInfo commands:
https://gist.github.com/jdhitsolutions/65070cd51b5cfb572bc6375f67bcbc3d

What I'm really trying to model or prototype is the experience. I'd rather have the data exposed with Get-Command, because there is already an appropriate type.

image

And getting a mix of files.
image

If a function belongs to a module, I'm not going to bother with command versioning. That should be handled by the module.

Gist
A proof of concept to add and get PowerShell meta data information - PSFunctionInfo.format.ps1xml

Nice Jeff!

So how do we handle multiple versions of the same function?
How does one explicitly load a specific function version?
How does one explicitly execute a specific version of a function?

@doctordns You are responsible for figuring out what to run and dealing with multiple versions. My prototype code is simply listing items in the FunctIon: Psdrive and displaying metadata information. As far as I know you can't have duplicate functions in the psdrive. My goal has been, if I have a stand alone function loaded, I want to be able to retrieve version and possibly other meta data information.

While I think it would be wonderful if PowerShell supported the _embedded_ PSScriptInfo metadata for _scripts_ ... please don't do this at the _function_ level.

Versions are only useful if you can do something about them

PowerShell currently _reports_ the version on commands, but the actual version comes from the _module_. Having separate versions for each function would not be helpful, it would just be confusing. Regardless of how you distribute your code, you're not distributing sub-file "hunks" and the user can't load an individual function (or anything except a full file) -- so there's no point in trying to version them.

I think we need versions for modules and scripts. Functions should inherit their version from the _module_ that contains them. Scripts need their own version.

For the record:

If you have a .ps1 file that has multiple functions in it, you need to change the extension. If you don't, then users have no way to discover those functions anyway, so versioning won't matter. We should actively discourage anyone trying to distribute _functions_ in anything but a module. 馃え

@Jaykul I agree that if a function ships with a module, which should be the goal, the module version matters. However, there are many of use who use stand-alone functions. Perhaps loaded in a profile or dot sourced to meet a specific need. Not everything I do needs to be loaded and built into a module. It is these non-module functions that I want to manage. I want a way to capture version and other metadata information just as we do for commands loaded from a module. Perhaps the answer is a new command like my prototype.

@Jaykul I think that several of us have already said that it would be, indeed, useful to us and that we are, indeed, distributing single functions.

IMO, not everything needs a module.

The proposed feature may not be useful to you. And that's fine. But it is to others of us.

I like the idea of function versioning. But if we do a solution, it should be more than a little documentation which you could do in comments Without the ability to control the loading of different versions etc this is a feature that we may be able to do without for now.

I suggest we drop this idea for 7.0. We are TOO close to RTM to be shoving new features into the mix. I have bad memories of NT 5 days!! If we can push this back to 7.1, THEN let's have a fuller conversation about how to implement it fully. Trying to just push this out at the last minute is a sure way to cause disappointment.

If this happens it is a 7.1 addition. I think the issue of loading the right version is up to the user. Again, all I am asking is for a way to retrieve version info from a loaded standalone function without having to dig through source.

I agree with 7.1.

At the same time, 猫I know users, and I'll bet a bottle or three that a documentation solution will only generate requests for version based runtime features. I'd like us to consider a complete and elegant as well as well engineered solution. .

We already have a solution for standalone functions - last one loaded wins. :-)

Without the ability to control the loading of different versions etc this is a feature that we may be able to do without for now.

This would force us to check the version of the function each time it is called. This is completely unacceptable for performance reasons. Module is best compromise - version check is done once at module loading.

@SteveL-MSFT I believe it is ready for PowerShell Committee conclusion. Prototype #11686.

It seems all agreed that version for scripts is useful to have in engine.
Version for functions is under question because module conception exists.

Common limitation is that we can only see versions for loaded scripts and functions and can not do that import-module does - load needed version but get-module also has not explicit version parameters.

Don't overthink this or make it more complicated than it needs to be. Functions shipped with modules belong to the module. Full stop. That should be the recommended best practice. However, not every function that might be used in production is deployed with a module. I don't actually care about how it is deployed. All I'm saying is that I want a way to look at a loaded function in the Function: PSDrive and be able to see a version number, and perhaps some other metadata information. I don't want to force myself or an end-user to track down the source code or folder to discover version information.

I was hoping Get-Command would be the tool to gather this information, but perhaps this needs to be spun off into something completely separate for stand-alone function management.

And maybe the answer is that this problem is best left to the community to solve. In which case, I think I have a good start on a solution.

Honest truth here is that dot sourcing functions is not the correct way to go, and should not have been recommended all those years back when the module system came in v2. The only time it was remotely sensible for use was in v1.

As we all know now, all functions should realistically be placed within a module, even those that you are loading as part of your profile (which you should load the module containing these functions) or using as part of a larger script for easier maintenance, or even those singular functions that you need to use in xyz scripts.

I can only see this change would add more confusion to those coming to the language than benefit by adding the suggested [Version] attribute to function definitions with many thinking that they would be required to set Module and Function versions when they are doing things properly and actually building modules of reusable code.

Yes this means you have to have a psd1 and a psm1 but 2 files for proper discoverability and maintainability isn't a huge ask to manage really

As you may tell, I personally disagree with this change & think there are better things to be focused on instead

Yes this means you have to have a psd1 and a psm1 but 2 files for proper discoverability and maintainability isn't a huge ask to manage really

Yes, it is a huge ask. Trying to make admin scripters into developers raises significantly the barrier to entry.

Saying "oh you have to copy this here and copy that there and then import it" is far more complicated and far more prone to error than saying "copy-and-paste this into a file named run.ps1 and then type ,\run.ps1".

This is a minor change and it stands to benefit casual/admin scripters far more than ternary operators or null-coalescing (which I still don't understand) or lots of other recent changes that were made for those of you who are devs. As was said about those, I can say about function versions: if you don't like it, don't use it.

@SteveL-MSFT I belive PowerShell committee could look the request to support versions for scripts and standalone functions. Prototype is in #11686.

Honest truth here is that dot sourcing functions is not the correct way to go,

But people do it, and it isn't the only use case for this request anyway.

and should not have been recommended all those years back when the module system came in v2. The only time it was remotely sensible for use was in v1.

As we all know now, all functions should realistically be placed within a module, even those that you are loading as part of your profile (which you should load the module containing these functions) or using as part of a larger script for easier maintenance, or even those singular functions that you need to use in xyz scripts.

I can only see this change would add more confusion to those coming to the language than benefit by adding the suggested [Version] attribute to function definitions with many thinking that they would be required to set Module and Function versions when they are doing things properly and actually building modules of reusable code.

Yes this means you have to have a psd1 and a psm1 but 2 files for proper discoverability and maintainability isn't a huge ask to manage really

Yes, but this isn't relevant. Nobody is arguing that we shouldn't version modules. ;-)

As you may tell, I personally _disagree_ with this change & think there are better things to be focused on instead

You wouldn't be required to use it. You aren't actually forced to use [CmdletBinding()] or parameter sets, either.
Commands, and cmdlets, have versions.
If a function itself has a version, _whether or not said function resides within a module_, it's simply a minor, but useful, documentation point. I have created numerous modules with varied and unrelated tools in them that don't justify their own individual modules. If my module is MiscStuff 1.1.0002, that's great for the distribution part; I use that. If there are thirty functions inside it and I want to quickly note whether I'm using Do-Thing 7.9.0 and be able to correlate it to an item in my changelog (which I do!), I would like some formal method for doing this, which would be in no way required by those who don't wish to.

@PowerShell/powershell-committee reviewed this, we believe the proper solution to have versioning for script functions is to wrap them in a module. It also not clear what the expected behavior would be if the script function is within a module, but has its own version attribute. Then there is concern about other parts of PowerShell, PowerShellGet, and PSGallery that would need to be updated to respect this new attribute. If the desire is to be able to pass around a single script file vs a folder (module), then it would be better to invest in the ability to import a zip/nupkg module directly and easier to turn a .ps1 into a module.

As an admin scripter and not a developer, I don't think that #7259 and this request (#11667) have anything to do with each other.

As an admin scripter I WILL NOT learn how to use modules. It's not relevant to me. It provides me NO VALUE. It's overhead FOR DEVELOPERS. Modules are a non-starter. (Not just for me, but most admin-scripters.)

For admin scripters and not developers - how does the committee suggest that versioning for functions/filters be done?

Not to be contrarian, but... versioning itself is a developer concept, by and large. I'm not sure why you're trying to draw this line here, of all possible places. 馃槙

Because I've released dozens of standalone functions and scripts. Without modules. Pretty sure this is covered above in prior comments from me, and several other popular bloggers/publishers.

I don't think that #7259 and this request (#11667) have anything to do with each other.

Yeah I don't really get the connection either to be honest.

I WILL NOT learn how to use modules.

FWIW you just change the extension to psm1 and (optionally) run New-ModuleManifest. There's not a whole lot to it.

It's not relevant to me. It provides me NO VALUE.

If you're publishing something for others to consume it definitely provides value. The way scopes work for modules make it a lot harder to step on someone's global session state and vice versa. That makes your function significantly more resilient to unexpected differences in environments.

It's also way easier to consume. If you release a function as a script folks need to dot source, they need to know where it is, dot source it, then use the command. If you release it as a module, folks can just deploy it ahead of time and call it whenever they want.

how does the committee suggest that versioning for functions/filters be done?

If versioning was implemented for individual functions, would you expect their version to be queryable?

If yes, can you elaborate a little bit on what scenario you would expect the user to query it? Would you expect it to play into any other systems like #requires tags?

If it would just be metadata visible in the source then you could just put it in the NOTES section of comment based help.

Based on PowerShell Committee conclusion I close the issue (stop tracking) but you free to continue the discussion.

Yes, I use the NOTES section today and a $pVersion/$pName. I suspect most of us with the need, do that.

When I was writing "I will not" I was speaking of the generic person, not of me specifically (although I fall into that category).

and a $pVersion/$pName

What do you do with those? Do you have an example of a loose function you've published? That may help in understanding the use case.

When I was writing "I will not" I was speaking of the generic person, not of me specifically (although I fall into that category).

Yeah there are definitely people who refuse to learn about modules. I have known and currently know plenty of folks like that (and have been said folk a few years ago), no arguments that they exist. That said, the overlap between those folks, and those who 1. care about versioning and 2. have any desire to publish their work - in my experience is almost zero. Realistically most of the folks who check boxes 1 and 2 will take the ~10 minutes to google how to wrap in a module real quick before they publish. Or more often, they don't care about versioning and they'll just slam it in a gist or a blog post.

and a $pVersion/$pName

What do you do with those? Do you have an example of a loose function you've published? That may help in understanding the use case.

When I was writing "I will not" I was speaking of the generic person, not of me specifically (although I fall into that category).

Yeah there are definitely people who refuse to learn about modules. I have known and currently know plenty of folks like that (and have been said folk a few years ago), no arguments that they exist. That said, the overlap between those folks, and those who 1. care about versioning and 2. have any desire to publish their work - in my experience is _almost_ zero. Realistically most of the folks who check boxes 1 and 2 will take the ~10 minutes to google how to wrap in a module real quick before they publish. Or more often, they don't care about versioning and they'll just slam it in a gist or a blog post.

I know how to use modules. I write them all the time. Heck, these functions are inside modules a lot of the time, and I use internal versioning so I can keep track of what last changed in a function without having to pull out to an external (to the function) changelog. I don't see why .VERSION in a function would interfere with a module version. I just don't.

I know how to use modules. I write them all the time. Heck, these functions are _inside_ _modules_ a lot of the time, and I use internal versioning so I can keep track of what last changed in a function without having to pull out to an external (to the function) changelog. I don't see why .VERSION in a function would interfere with a module version. I just don't.

Right but the main thing is: what are you going to do with that metadata? Is it just something you look at in the source of the function? If so, can you elaborate on how the .NOTES section isn't suitable for that?

Also this issue was for allowing a standalone function to declare it's version number in Get-Command output. A request to include a full change log would be a pretty different implementation and probably better off in it's own issue.

that's easy to answer. that means i have to open it in an editor or perform some type of awkward string search to find out the version installed on a given computer/given location on said computer. that isn't always easy as this answer. :-)

Not really?

Notes field shows in the Get-Help output if you're writing it as proper comment-based help (which you should be anyway).

function test {
    <#
    .NOTES
    Version: 1.5.0
    #>
    [CmdletBinding()]
    param()
}

Get-Help test -Full

# or
Get-Help test | % alertSet

Result:

PS> Get-Help test -Full
SYNTAX
    test [<CommonParameters>]


DESCRIPTION


PARAMETERS
    <CommonParameters>
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer, PipelineVariable, and OutVariable. For more information, see
        about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216).

INPUTS

OUTPUTS

NOTES


        Version: 1.5.1


RELATED LINKS

PS> get-help test | % alertset



    Version: 1.5.1

So? I'm not impressed. There were other, more verbose ways to do ternary operators too. And yet... a more compact way got implemented. Because (a part of) the community wanted it. I still don't know what the heck null-coalescing operators are.

Not really sure what you're after, then. That quite clearly lets you do what you're after relatively easily. I don't see it being worth the time to add extra fields there.

If you do, feel free to open a PR and we'll talk about it further. 馃し

A PR for what? I'm not a C# coder. I haven't been a professional developer for over 30 years.

I think several of us were really clear about what we are after, and our use cases, and saw those concerns minimized, as you just did again.

Those of you who are beyond admin scripting think "just make it a module". Most admin scripters aren't going to know how and they'll say (and I'll agree) that it's too much trouble once they see the requirements.

But this is pointless and I knew better. The PowerShell Committee conclusion was already reached. Sorry I commented.

The PowerShell Committee conclusion was already reached.

Any by-design conclusions can be reconsidered based on feedbacks.

I think several of us were really clear about what we are after, and our use cases, and saw those concerns minimized, as you just did again.

I've been trying really hard to get to the bottom of what the actual goal and use cases are and my questions have been mostly ignored. I'd really like to understand.

Those of you who are beyond admin scripting

Man, I'm a sysadmin. Most of my career was spent thinking the hashtable syntax was "the splatting syntax". Most of my coworkers only know the absolute basics of PowerShell (if at all), same with most of the people I help on discord/reddit.

I get that you have some beef with the PS dev team, but please don't pretend like I couldn't possibly understand your perspective because I happen to know C# now. I'm constantly fighting for that perspective on this repo.

think "just make it a module". Most admin scripters aren't going to know how and they'll say (and I'll agree) that it's too much trouble once they see the requirements.

For anyone who comes across this thread later and gets discouraged from making a module, here are the requirements:

  1. Rename your .ps1 file to .psm1
  2. Use Import-Module ./file.psm1 instead of . ./file.ps1

That's it, you got a module. Everything else is just best practices you don't need to worry about yet. If you want to version it you need a manifest, here's the full requirements for that:

  1. Rename your .ps1 file to .psm1
  2. Run New-ModuleManifest -RootModule ./file.psm1 -Path ./file.psd1 -ModuleVersion 1.0.0
  3. Use Import-Module ./file.psd1 instead of . ./file.ps1

If you're shipping a loose function in a ps1 that the consumer (or yourself) would need to dot source, just throw it in a module real quick it'll make your life easier.

If you're shipping a directly invokable script, you can use New-ScriptFileInfo which will generate a script file with a comment like this:

<#PSScriptInfo

.VERSION 1.0.0

.GUID xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

.AUTHOR username

.COMPANYNAME

.COPYRIGHT

.TAGS

.LICENSEURI

.PROJECTURI

.ICONURI

.EXTERNALMODULEDEPENDENCIES 

.REQUIREDSCRIPTS

.EXTERNALSCRIPTDEPENDENCIES

.RELEASENOTES


.PRIVATEDATA

#>

<# 
.DESCRIPTION 
     Quick script description here.
#> 
param()

And you can query that with Test-ScriptFileInfo.

Was this page helpful?
0 / 5 - 0 ratings