how to track all function calls of some modules? - events

I'd like to have some usage statistics for a bunch of my modules.
It would be handy if I could run code whenever a function is called from a set of modules. Is it doable? Do powershell generate internal events we can hook on? I can not find any guidance yet

It's not completely clear to me whether you're more interested in logging events or executing code (hooking).
Logging
There are 2 places where in the event log where Powershell writes to the logs:
Applications and Services > Windows PowerShell
Applications and Services > Microsoft > Windows > PowerShell
On a per-module level, you can enable the LogPipelineExecutionDetails property. To do it on load:
$mod = Import-Module ActiveDirectory
$mod.LogPipelineExecutionDetails = $true
Or for an already loaded module:
$mod = Get-Module ActiveDirectory
$mod.LogPipelineExecutionDetails = $true
After that you check the first of the event log locations I listed (Windows PowerShell) and you'll see logs that show the calls to various cmdlets with the bound parameters.
You can also enable this via Group Policy as a Computer or User setting:
Administrative Templates > Windows Components > Windows PowerShell > Turn On Module Logging
You can specify the module(s) you want to enable logging for.
In PowerShell v5, there will be even more detailed logging available (see the link).
Source
You can see more detailed information about the logging settings (current and upcoming) on Boe Prox's blog: More New Stuff in PowerShell V5: Extra PowerShell Auditing
Hooking
As far as I know there is no direct way to hook calls in an existing module, but I have a crappy workaround.
You can effectively override existing cmdlets/functions by creating functions or aliases with the same name as the original.
Using this method, you could create wrappers around the specific functions you want to track. Consider something like this:
# Override Get-Process
function Track-GetProcess {
[CmdletBinding()]
param(
# All the parameters that the original function takes
)
# Run pre-execution hook here
& { "before" }
$params = #{}
foreach($h in $MyInvocation.MyCommand.Parameters.GetEnumerator()) {
try {
$key = $h.Key
$val = Get-Variable -Name $key -ErrorAction Stop | Select-Object -ExpandProperty Value -ErrorAction Stop
if (([String]::IsNullOrEmpty($val) -and (!$PSBoundParameters.ContainsKey($key)))) {
throw "A blank value that wasn't supplied by the user."
}
Write-Verbose "$key => '$val'"
$params[$key] = $val
} catch {}
}
Get-Process #params # call original with splatting
# run post execution hook here
& { "after" }
}
The middle there uses splatting to send the given parameters and sending them to the real cmdlet.
The hardest is part is manually recreating the parameter block. There are ways you could likely do that programmatically if you wanted to quickly run something to hook any function, but that's a bit beyond the scope of this answer. If you wanted to go that route, have a look at some of the code in this New-MofFile.ps1 function, which parses powershell code using powershell's own parser.

Related

Can't call a Powershell script through the registry properly. A positional parameter cannot be found that accepts argument '$null'

Here is a simple test function called RegistryBoundParams.ps1:
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$Target,
[Parameter(Mandatory = $false)]
[switch]
$MySwitch
)
if(!(Test-IsAdmin)){
Request-AdminRights -NoExit
Exit
}
if($MySwitch){
"Do something" | Out-Host
}else {
"Do something else" | Out-Host
}
Show-AllArguments
If I call it via the PS terminal, everything works as expected:
Exact call: C:\Tools\scripts> .\RegistryBoundParams.ps1 -Target "C:\Test\" -MySwitch
If I call it through the registry (adding the command to a context menu), I get:
pwsh -noexit -file "C:\Tools\scripts\RegistryBoundParams.ps1" -Target "C:\Program Files\Python39\python.exe" -MySwitch
Plaintext of the error: RegistryBoundParams.ps1: A positional parameter cannot be found that accepts argument '$null'.
Here's a reg file that shows exactly what I added in the registry:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\*\shell\1_TestRegistry]
#="Test Powershell Script from Registry"
"Icon"="C:\\Tools\\icons\\apps\\Powershell 1.ico,0"
"NeverDefault"=""
[HKEY_CLASSES_ROOT\*\shell\1_TestRegistry\command]
#="pwsh -noexit -file \"C:\\Tools\\scripts\\RegistryBoundParams.ps1\" -Target \"C:\\Program Files\\Python39\\python.exe\""
So somewhere along the lines $Null is being passed to the script, and I have no Idea why.
I could really, really use some help here.
Thanks so much for any guidance.
Edit:
I found that if I add a new string variable called $catchall, the script works. I suspect that when being called from the registry it's appending a null value for some reason. Which is why the script works when I define an additional "catch all" variable.
This is definitely not an ideal solution at all, so I am still looking for a solution here. Really appreciate any help!
Edit2:
It turns out that the Request-AdminRights script I was using that mklement0 authored had a bug that has now been fixed. Anyone who wants one-line self elevation with bound/unbound parameter support that's cross-platform... go get it!
The problem was a (since-fixed) bug in the code that you based your self-elevating function Request-AdminRights on:
The bug was that in the case of an advanced script such as yours, $args - which is never bound in advanced scripts - was mistakenly serialized as $null instead of translated to #(), resulting in that $null getting passed as an extra argument on re-invocation.
If you redefine your Request-AdminRights function based on the now updated body of the Ensure-Elevated function in the original answer, your problem should go away - no need to modify the enclosing script.

Powershell calling a function if script has mandatory parameters

I'm pretty new to PowerShell but loving it for automating loads of tasks on our Windows machines. I love that you can call functions from other scripts, however the scripts I have written all use parameters the user can provide (so it's easier for colleagues to use them).
There is one parameter in particular that is usually mandatory in my scripts. The problem I'm facing is calling functions from scripts with mandatory parameters.
Here's a simple example:
Param(
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$VirtualMachine=$(throw "Machine name missing!"),
[int]$Attempts = 150
)
Function DoSomething($VirtualMachine, $Attempts){
write("$VirtualMachine and $Attempts")
}
Running this as a script you would provide -VirtualMachine "VMnameHere" -Attempts 123. Running this would produce VMnameHere and 123. Perfect! However.. If I try to call this as a function from another script..
Example here:
. ".\Manage-Machine.ps1"
DoSomething -VirtualMachine "nwb-thisisamachine" -Attempts 500
This produced an error:
Machine name missing!
At C:\Users\something\Desktop\Dump\play\Manage-Machine.ps1:33 char:28
+ [string]$VirtualMachine=$(throw "Machine name missing!"),
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Machine name missing!:String) [], RuntimeException
+ FullyQualifiedErrorId : Machine name missing!
Which is clearly because the field is mandatory. Am I doing something wrong in how I'm calling the function in this case? Is there an alternative way to calling the function if the script it belongs to has mandatory parameters, because if I remove the validation for the parameter, it all works.
Would love some input,
Thank you!
I would use [parameter(Mandatory = $true)] and remove =$(throw "Machine name missing!").
You can then run powershell with the -NonInteractive flag (documentation link) and any missing mandatory parameters will cause an error and a non-zero exit code will be returned.
This return code should be picked up by your CI process and it itself will handle the error.
I'm not sure it's such a great idea to do this, but it sounds like the following would work:
Param(
[ValidateNotNullOrEmpty()]
# Do NOT use = $(Throw ...) or [Parameter(Mandatory)].
[string]$VirtualMachine,
[int]$Attempts = 150
)
# Determine if the script is being "dot-sourced".
# Note: The `$MyInvocation.Line -eq ''` part detects being run from the
# ISE or Visual Studio Code, which implicitly perform sourcing too.
$isDotSourced = $MyInvocation.InvocationName -eq '.' -or $MyInvocation.Line -eq ''
# NOT sourced? Enforce mandatory parameters.
if (-not $isDotSourced) {
if (-not $VirtualMachine) { Throw "Machine name missing!" }
}
Function DoSomething($VirtualMachine, $Attempts) {
"$VirtualMachine and $Attempts"
}
# NOT sourced? Call the default function or
# do whatever you want the script to do when invoked as a whole.
if (-not $isDotSourced) {
DoSomething $VirtualMachine $Attempts
}
. .\Manage-Machine.ps1 will then merely define the functions (DoSomething in this case), for later invocation;
since none of the script parameters are technically declared as mandatory, invocation without parameters will succeed (unlike in your attempt, where the throw statement invariably kicked in - whether directly invoked or dot-sourced).
.\Manage-Machine.ps1, by contrast, will enforce the presence of a $VirtualMachine parameter value and instantly call DoSomething, passing the parameter values through.
Note that, of course, your functions could benefit from typing your parameters and adding validation attributes, too.

How can I automatically syntax check a powershell script file?

I want to write a unit-test for some code which generates a powershell script and then check that the script has valid syntax.
What's a good way to do this without actually executing the script?
A .NET code solution is ideal, but a command line solution that I could use by launching an external process would be good enough.
I stumbled onto Get-Command -syntax 'script.ps1' and found it concise and useful.
ETA from the comment below: This gives a detailed syntax error report, if any; otherwise it shows the calling syntax (parameter list) of the script.
You could run your code through the Parser and observe if it raises any errors:
# Empty collection for errors
$Errors = #()
# Define input script
$inputScript = 'Do-Something -Param 1,2,3,'
[void][System.Management.Automation.Language.Parser]::ParseInput($inputScript,[ref]$null,[ref]$Errors)
if($Errors.Count -gt 0){
Write-Warning 'Errors found'
}
This could easily be turned into a simple function:
function Test-Syntax
{
[CmdletBinding(DefaultParameterSetName='File')]
param(
[Parameter(Mandatory=$true, ParameterSetName='File', Position = 0)]
[string]$Path,
[Parameter(Mandatory=$true, ParameterSetName='String', Position = 0)]
[string]$Code
)
$Errors = #()
if($PSCmdlet.ParameterSetName -eq 'String'){
[void][System.Management.Automation.Language.Parser]::ParseInput($Code,[ref]$null,[ref]$Errors)
} else {
[void][System.Management.Automation.Language.Parser]::ParseFile($Path,[ref]$null,[ref]$Errors)
}
return [bool]($Errors.Count -lt 1)
}
Then use like:
if(Test-Syntax C:\path\to\script.ps1){
Write-Host 'Script looks good!'
}
PS Script Analyzer is a good place to start at static analysis of your code.
PSScriptAnalyzer provides script analysis and checks for potential
code defects in the scripts by applying a group of built-in or
customized rules on the scripts being analyzed.
It also integrates with Visual Studio Code.
There are a number of strategies for mocking PowerShell as part of unit tests, and also have a look at Pester.
The Scripting Guy's Unit Testing PowerShell Code With Pester
PowerShellMagazine's Get Started With Pester (PowerShell unit testing framework)

Monitoring scripts and sending email when it crashes

I have some perl scripts which are scheduled using task scheduler in windows 2003 R2 and 2008. These scripts are called directly using perl.exe or via a batch file.
Sometimes these scripts fails to execute (crashes maybe) and we are not aware of these crashes.
Are there any ways a mail can be sent when these script crashes? more or less like monitoring of these scripts
Thanks in advance
Karthik
Why monitor the scripts from the outside when you can make the plugins to monitor theirself? First you can use eval in order to catch errors, and if an error occours you can send an email with the Net::SMTP module as rpg suggested. However I highly recommend you to use some kind of log file in order to keep trace of what happened right before the error and what caused the error. Your main goal should be to avoid the error. That ofcourse requires you to modify the scripts, if, for any reason, you cannot do that then the situation may be a little more complicated because you need another script.
With the Win32::Process::Info module you can retrieve running processes on Windows and check if your plugin is running or not.
while(1) {
my $found = false;
my $p = Win32::Process::Info->new;
foreach my $proc ($pi->GetProcInfo) {
if ($proc->{Name} =~ /yourscriptname/i ) {
found = true;
}
}
if ($found eq 'false') {
# send email
my $smtp = Net::SMTP->new("yoursmtpserver");
eval {
$smtp->mail("sender#test.it");
$smtp->recipient("recipient#test.it");
$smtp->data;
$smtp->datasend("From: sender#test.it");
$smtp->datasend("\n");
$smtp->datasend("To: recipient#test.it");
$smtp->datasend("\n");
$smtp->datasend("Subject: Plugin crashed!");
$smtp->datasend("\n");
$smtp->datasend("Plugin crashed!");
$smtp->dataend;
$smtp->quit;
};
}
sleep(300);
}
I did not test this code because I don't have Perl installed on Windows but the logic should be ok.
For monitoring - Please check the error code. This will help you for its failure.
For mail sending - You can use Net::SMTP module to send email. Let me know if you need a code snippet for it.
You can use PushMon to monitor your scripts. What you do is create PushMon URLs that matches the schedule of your Perl scripts. Then you should "ping" these URLs when your scripts run successfully. If these URLs are not accessed, maybe because your scripts crashed or there's a power failure, PushMon will notify you by email.
Disclaimer: I am associated with PushMon.

Detect if running with administrator privileges under Windows XP

I am trying to work out how to detect whether a user is running with admin rights under Windows XP. This is fairly easy to do in Vista/Win7 thanks to the whoami command. Here's a snippet in Ruby for how to do it under Vista:
Note, the following link now incorporates the solution suggested by muteW
http://gist.github.com/65931
The trouble is, whoami doesn't come with Windows XP and so the above linked method will always return false on WinXP, even if we're running as an administrator.
So, does anyone know of a way to detect whether we're running as an admin under Windows XP using Ruby, command-line tools, batch-files, or even third-party (needs to be open source, really) tools?
This will detect if the user is running in elevated mode (eg a command prompt that was "Run As" Administrator). It relies on the fact that you require admin privileges to read the LOCAL SERVICE account reg key:
reg query "HKU\S-1-5-19"
this will return a non-zero error code if it cannot be read, and zero if it can.
Works from XP up...
If you run
>net localgroup administrators
in a command shell you should get the list of administrator accounts in Windows XP. Simply parse and scan the output to check for the particular user account you want. For e.g. to check if the current user is an administrator you could do -
>net localgroup administrators | find "%USERNAME%"
Piskvor option its fine, or check this url
http://weseetips.com/2008/04/16/how-to-check-whether-current-user-have-administrator-privilege/
this is the code in that page
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
PSID AdministratorsGroup;
// Initialize SID.
if( !AllocateAndInitializeSid( &NtAuthority,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&AdministratorsGroup))
{
// Initializing SID Failed.
return false;
}
// Check whether the token is present in admin group.
BOOL IsInAdminGroup = FALSE;
if( !CheckTokenMembership( NULL,
AdministratorsGroup,
&IsInAdminGroup ))
{
// Error occurred.
IsInAdminGroup = FALSE;
}
// Free SID and return.
FreeSid(AdministratorsGroup);
return IsInAdminGroup;
Check out the CheckTokenMembership method. There is a sample there of IsUserAdmin() implementation plus some other useful community feedback on when that function does not return what is expected and what to do to improve it.
This will find out without shelling out:
require 'win32/registry'
is_admin = false
begin
Win32::Registry::HKEY_USERS.open('S-1-5-19') {|reg| }
is_admin = true
rescue
end
The strategy is similar to Peter's, but with less overhead.
Here is the better (PowerShell) way of doing it: https://stackoverflow.com/a/16617861/863980
In one line, you can say (copy/paste in posh and it will work):
(#(([ADSI]"WinNT://./Administrators,group").psbase.Invoke("Members")) | `
foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}) -contains "Administrator"
=> returns True when user belongs to Administrators group (as opposed to checking user IS Administrator)
(Note: backtick or grave accent ` escapes the carriage return in PowerShell, in Ruby it executes the shell commands, like C++'s system('command')..)
So in Ruby, you can say (copy/paste in irb):
def is_current_user_local_admin?
return `powershell "(#(([ADSI]'WinNT://./Administrators,group').psbase.Invoke('Members')) | foreach {$_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null)}) -contains 'Administrator'"`.include? "True"
end
Don't know the (even better) WMI way of doing it though. With that, you could have done something like (in Ruby again):
require 'win32ole'
wmi = WIN32OLE.connect('WinNT://./Administrators,group')
# don't know what should come here...

Resources