Garacesh Posted June 19, 2014 Posted June 19, 2014 (edited) So, I found out about function parameters and how they can include validating, mandatory or not, etc. Very useful stuff. So I'm revisiting an old script that contains a lot of if/elseif/else commands to the same purpose. function TestFunc { Param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)] [ValidateScript({ You see where this is going.. Problem is, I've defined 5 parameters for this function (two mandatory, 3 not) and if I copy-and-paste my script into a powershell window, it's working fine.. PS M:\> TestFunc Param0 Param1 Param2 Param3 Param4 But this is part of a script, so if I just have the function (with the intention of letting it load and then manually calling it) the script hits the end of file and then exits. If I make a shortcut to powershell that pulls the script with the -noexit flag, the powershell window stays open but it doesn't "remember" the function (presumably the script exits once complete still) So, I've tried $Command = Read-Host " " and then $TestFunc $Command, but that isn't working either. It plays out as expected, but whereas a manual execution of a function splits at spaces, instead this method makes Param0 "Param0 Param1 Param2 Param3 Param4", leaving Param1 through 4 as $null. Try as I might with [regex]::split, ForEach-Object {$_ -split, I just can't get it to pass the parameters as individual bits. Anybody encountered anything similar before? Edited June 19, 2014 by Garacesh
ascott2 Posted June 19, 2014 Posted June 19, 2014 I have had something similar in the past passing arguments to an exe where it just wouldn't accept anything from a Read-Host unless it was part of an array. So something like this might work? $commandargs = @() for ($i=1; $i -le 5; $i++){ $indarg = Read-Host "Enter paramenter $i" $commandargs += $inarg } $TestFunc $commandargs[0] $commandargs[1] $commandargs[2] $commandargs[3] $commandargs[4] Rather than loop through Read Hosts for each one you could use split and add to the array that way. Not sure if it is the same issue but worth a try.
Garacesh Posted June 19, 2014 Author Posted June 19, 2014 (edited) That doesn't seem to be working.. If I write a function: function test { Write-Host $args[0] Write-Host $args[2] Write-Host $args[1] } and then define $teststr.. $Teststr = "one", "two", "three" When I run it through the function I ought to get a return of one three two right? But I don't When I run "test $teststr", instead I get one two three (one two three and then two blank lines) So it's still passing it all as one argument. Edited June 19, 2014 by Garacesh
Garacesh Posted June 20, 2014 Author Posted June 20, 2014 (edited) Some progress: Okay, if I take the time to actually define parameters (like I would do in my live script) things change slightly.. [color="#0000FF"]function[/color] [color="#008080"]func[/color] { [color="#0000FF"]Param[/color] ( [Parameter(Mandatory[color="#FF0000"]=[/color][color="#4B0082"]$true[/color], ValueFromPipeline[color="#FF0000"]=[/color][color="#4B0082"]$true[/color], Position[color="#FF0000"]=[/color]0)] [ValidateScript({ [color="#008080"]Write-Host[/color] ([color="#800000"]"Value0: "[/color] [color="#FF0000"]+[/color] [color="#4B0082"]$_[/color]) [color="#4B0082"]$true[/color] })] [[color="#008080"]String[/color]][color="#4B0082"]$Value0[/color] , [Parameter(Mandatory[color="#FF0000"]=[/color][color="#4B0082"]$false[/color], ValueFromPipeline[color="#FF0000"]=[/color][color="#4B0082"]$true[/color], Position[color="#FF0000"]=[/color]1)] [ValidateScript({ [color="#008080"]Write-Host[/color] ([color="#800000"]"Value1: "[/color] [color="#FF0000"]+[/color] [color="#4B0082"]$_[/color]) [color="#4B0082"]$true[/color] })] [[color="#008080"]String[/color]][color="#4B0082"]$Value1[/color] , [Parameter(Mandatory[color="#FF0000"]=[/color][color="#4B0082"]$false[/color], ValueFromPipeline[color="#FF0000"]=[/color][color="#4B0082"]$true[/color], Position[color="#FF0000"]=[/color]2)] [ValidateScript({ [color="#008080"]Write-Host[/color] ([color="#800000"]"Value2: "[/color] [color="#FF0000"]+[/color] [color="#4B0082"]$_[/color]) [color="#4B0082"]$true[/color] })] [[color="#008080"]String[/color]][color="#4B0082"]$Value2[/color] ) [color="#0000FF"]begin[/color] { } [color="#0000FF"]process[/color] { } [color="#0000FF"]end[/color] { [color="#008080"]Write-Host[/color] [color="#800000"]"End"[/color] } } If I run through that 'normally' with "func 1 2 3" I get the expected result.. "Value0: 1", "Value1: 2" and "Value2: 3", however using $args[0], [1] and [2] no longer works like it did previously. If I split a string (such as the string Read-Host would generate) at spaces ($teststring.split()) which makes an array of "one", "two" and "three" (rather than just one string of "one two three", but if I run that through func (with $teststring.split() | func) I get a response of "Value0: one", "Value1: one", "Value2: one", "Value0: two", "Value1: two", "Value2: two", "Value0: three", "Value1: three" and "Value2: three" So it appears to be passing each member of a variable through to func, but applying it as every parameter. Maybe I can make something of this.. maybe not. [ATTACH=CONFIG]25188[/ATTACH] Edited June 20, 2014 by Garacesh
Garacesh Posted June 23, 2014 Author Posted June 23, 2014 Bump! I found a solution! [b][font=Courier New][color="#800080"]$teststr[/color] [color="#FF0000"]=[/color] ([color="#800080"]$null[/color], [color="#800080"]$null[/color], [color="#800080"]$null[/color], [color="#800080"]$null[/color], [color="#800080"]$null[/color]) [color="#800080"]$teststr[/color][0], [[color="#008080"]int[/color]][color="#800080"]$teststr[/color][1], [color="#800080"]$teststr[/color][2], [color="#800080"]$teststr[/color][3], [color="#800080"]$teststr[/color][4] = (([color="#008080"]Read-Host[/color] [color="#800000"]" "[/color]).split([color="#800000"]' '[/color])) [color="#008080"]TestFunc[/color] $([color="#800080"]$teststr[/color][0]) ([[color="#008080"]int[/color]]$([color="#800080"]$teststr[/color][1])) $([color="#800080"]$teststr[/color][2]) $([color="#800080"]$teststr[/color][3]) $([color="#800080"]$teststr[/color][4])[/font][/b] It feels a tad kludgy, but it works.
Arthur Posted June 23, 2014 Posted June 23, 2014 (edited) It feels a tad kludgy. It sure is!!! You're making it way more complicated than it needs to be. This is what you should be doing... function Test-Me { Param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)] [string] $Value0, [Parameter(Mandatory=$false, ValueFromPipeline=$true, Position=1)] [string] $Value1, [Parameter(Mandatory=$false, ValueFromPipeline=$true, Position=2)] [string] $Value2 ) Begin { } Process { $Value0 $Value1 $Value2 } End { } } Test-Me -Value0 One -Value1 Two -Value2 Three Test-Me One Two Three ... and here's the output: Out of interest, what sort of things are you trying to validate? For some of the parameters you could use ValidateSet, ValidateRange, ValidatePattern (and so on) for example. Btw, you may find Volume 7 of the PowerShell Monthly Tips useful. Edited June 23, 2014 by Arthur 2
Garacesh Posted June 24, 2014 Author Posted June 24, 2014 (edited) Well, since I learnt about Function Parameters (and their subsequent ValidateScript section) I wanted to revisit one of my earlier scripts because the current error checking is a huge sequence of if (...) { [indent]# blah blah blah[/indent] } elseif (...) { [indent]# blah blah blah[/indent] } elseif (...) { ad infinatum. The code is for my "Search for Login events of a remote machine and pull back X amount. Email results if required." script, primarily used for when laptops are vandalised. Here's my full Param( block right now as it stands (ErrorActionPreference will be changed to Stop/Inquire rather than Inquire/Inquire once everything is fully working and tested) function TestFunc { Param ( [Parameter(Mandatory=$true, HelpMessage="The name of the target machine. Must be powered and networked.", Position=0)] [ValidateScript({ $ErrorActionPreference = "Inquire" Write-Host "$_" try { Test-Connection ($_ + ".school.local") -Count 2 } catch { Write-Warning ("No response from target host. Ensure power and connectivity.") } $ErrorActionPreference = "Inquire" })] [string]$ComputerName , [Parameter(Mandatory=$true, HelpMessage="Number of events to return. INT only.", Position=1)] [ValidateScript({ $ErrorActionPreference = "Inquire" if (($_ -isnot [int]) -or ($_ -lt 1)) { Write-Warning "MaxEvents value must be a positive integer" } else { $true } $ErrorActionPreference = "Inquire" })] $MaxEvents , [Parameter(Mandatory=$false, Position=2)] [AllowNull()] [AllowEmptyString()] [AllowEmptyCollection()] [string]$Part , [Parameter(Mandatory=$false, Position=3)] [AllowNull()] [AllowEmptyString()] [AllowEmptyCollection()] [ValidateSet("y","n")] [ValidateScript({ if ($_ -inotmatch "^(y|n)$") { Write-Warning "RepairedStatus must be Y or N." } else { $true } })] $RepairStatus , [Parameter(Mandatory=$false, Position=4)] [AllowNull()] [AllowEmptyString()] [AllowEmptyCollection()] [ValidateScript({ $ErrorActionPreference = "Inquire" try { (Get-ADUser -Identity $_) } catch { Write-Warning ("Cannot find user \'" + $_ + "\'") } $ErrorActionPreference = "Inquire" })] $Email ComputerName is quite literally the name of the target machine, used for Get-WinEvent (thus, mandatory) and for crafting the email if needed. The same goes for MaxEvents, it's required for Get-WinEvent, so that's a mandatory parameter. Part is an open string with no requirements used for the e-mail, for example "keyboard", "screen", etc. The e-mail just fills in the blanks. RepairStatus alters the e-mail body by a simple if (...) statement and $EmailBody +=, adding fixed/not fixed. The $Email checks a given string against AD (as our email addresses are all ADUserName@School....). If any of the non-mandatory parameters are not specified, it simply writes the output to the screen (Write-Host), and if they are all specified (and checked as correct) it sends an e-mail (Send-MailMessage) Just to clarify, this function will run as part of a script. The function relies on parameters it's given, but there's no way to manually call the function if it's a .ps1, therefore I'm using a Read-Host to prompt for the parameters it needs, which will then run them through the function (and as part of the function, validation) Edit: Oh, poop Get-ADUser doesn't want to work in Powershell 2 (It has to be ran as 2 as Get-WinEvent doesn't work in PS3 unless you're in the USA).. Funny, though. In a test environment running PS2 I can use Import-Module ActiveDirectory and then Get-ADUser works.. But if I have Import-Module ActiveDirectory in my script, it still says Get-ADUser isn't a recognised cmdlet, so I don't think it's importing.. Edited June 24, 2014 by Garacesh
Garacesh Posted June 26, 2014 Author Posted June 26, 2014 Double-post because I can't edit. I've found a pseudo-solution that fixes my "Get-WinEvent only working in Powershell 2" issue.. If I prepare it as two separate scripts and call the second from the first I can use "powershell.exe -version 2 -command (". {C:\Scripting\Location\GetLogins.ps1}")" and that's actually working correctly now. The issue is that the parameters for the function (ComputerName, MaxEvents, etc) aren't being passed over to the child script. Every attempt I make to clean-up this script and make it more efficient seems to end in just kludging it a different way
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now