Open StartAutomating opened 11 months ago
Possible duplicate of #13493. What do you think @StartAutomating?
@SeeminglyScience they would both be good.
Module load being an event would be valuable for module-to-module integrations (aka, your module plugs into my module, and being notified that a module loads lets me know I should attempt to import extensions from the new module)
Command being loaded is more granular and flexible.
For one, not all commands will be in a module (this should notify even if a function is created on the cli / within another function)
For another, providing a "module" event would require me to loop thru each command in the module to check for handshakes, rather than allowing the command itself to be the handshake.
That stated, I think the code required to support both scenarios would be similar, and, if I construct a fix, I'll try to do both.
@SeeminglyScience a related curiosity is this:
Can any of the PowerShell engine auditing stuff be efficiently repurposed towards these two points?
Hey @StartAutomating
We discussed your proposal in the Engine Working Group earlier this week, but we struggled a bit with how to interpret the expected outcome here. Based on your input we assumed it unlikely that you'd be interested in tracing output - you want to be able to react to (and possibly intercept) function registration at runtime from within the shell - is this correctly understood?
In that case you'd need a facility for hooking an event handler. Let's say, hypothetically, we decide to implement Register-ScopedEngineEvent
for this purpose, and provide a cognate runtime API to handle the event registrations.
Now, the primary source of complexity for this ask is that function registrations are scoped in PowerShell. Consider the following example:
& {
Register-ScopedEngineEvent -SourceIdentifier FunctionRegistration -Action {
param($sender,$scopedEngineEventArgs)
Write-Host "Function $($scopedEngineEventArgs.Name) is about to be defined in local scope of scriptblock $(scopedEngineEventArgs.ScriptBlockId)"
}
function foo { } # I assume you want evaluation of this statement raise the event
& {
function bar {} # but what about this one?
}
function fooDeep {
function fooDeepHelper {} # what about this one?
fooDeepHelper
}
fooDeep
fooDeep # now `function fooDeepHelper {}` has been registered twice in different local scopes
# do you want to know every time?
}
In other words: what behavior would you expect from such a facility :)
@IISResetMe my apologies, I had meant to get around to a brief PR on this one. I'll see what I can do.
I wasn't expecting a new cmdlet, just a new engine event (like "PowerShell.Exiting" )
I believe the engine event would need to have event arguments or message data comprising of:
[CommandInfo]
$executionContext
in which it was created (which would effectively be the parent scope, whatever it might be)[AST]
of the command declaration (this would be nice to have, but probably not as required as the other pair)Does this make the desired implementation clear?
I'm happy to discuss directly with the working group if you would like and will see if I can craft a proof-of-concept.
Hope this Helps!
I'm still gonna need some clarification regarding scope propagation - unless the idea is that any caller from any call stack can subscribe to these events and get them from ... any function registration anywhere within the attached runspace?
@IISResetMe I think the simplest answer would be to allow it from anywhere within the attached runspace.
I think in your example, yes, you would want to know "every time" fooDeepHelper has been registered. I'd also want to know "every time" an alias is created at any of those scopes, too.
If we're thinking about this from the security/logging perspective only, getting selective about scope is setting up places where commands can hide. And, surely, we do not want that.
Make sense?
LMK if you need more info or clarification.
I had a look at the relevant code for evaluation of function
/filter
statements in the compiler, and we can trivially implement an engine event that emits post-eval, for the hosting runspace, allowing you to do something like:
Register-EngineEvent -SourceIdentifier PowerShell.SetFunction -Action {
Write-Host "Registered function with name: $($EventArgs.Name)" # [string]
Write-Host "Function body: $($EventArgs.ScriptBlock)" # [scriptblock]
Write-Host "Resulting CommandInfo object: $($EventArgs.CommandInfo)" # [FunctionInfo]
Write-Host "Registered aliases: $($EventArgs.Aliases)" # [AliasInfo[]]
Write-Host "Function already existed? $($EventArgs.Updated)" # [bool]
}
It would then be raised every time a statement using the function
or filter
keywords would be evaluated in the current runspace:
function f { } # 1 event is emitted
1..10 |ForEach-Object {
function f { } # 10 events eventually emitted
f
}
Would this be sufficient for the use cases you have in mind @StartAutomating ? My only concern with this approach is that we might want to provide a way to filter for new vs updated functions, to reduce noise when you're only interested in initial registration.
(Note: direct modification of items or content in the function:
provider drive would bypass the emission of the event)
@IISResetMe this seems like it would handle about 90%+ of the scenarios.
While I do use the function: provider regularly enough, I also can see some benefit to having an "exception to the rule" (except for the potential security downside of not knowing about that new function).
I also see the benefit of providing two events (new vs updated).
So, in short, sounds pretty good.
My suggested event IDs are:
PowerShell.Function.New
PowerShell.Function.Set
Though I see little harm in being a little "alias happy" here.
@IISResetMe did you ever get the chance to make a PR for the engine event?
Summary of the new feature / enhancement
As a developer, I want to be able to know when a new PowerShell command is created.
There are a variety of reasons where this would prove useful:
Proposed technical implementation details (optional)
Ideally, the PowerShell engine should expose engine events containing this information.
If the team does not wish to generate engine events without being sure a listener will care, events could be surfaced from a number of classes.