PowerShell: Simple way to add dynamic parameters to advanced function


I’ve been working on a module for Nimble Storage and I’ve been creating dynamic parameters for some of the functions and I thought, why not just create a helper function for this? So I did, here it is with a simple example. The code is actually not too bad once you look through it, mostly just populating some objects and dumping them in the DynamicParam block.

Thanks Bart for the initial help! :)

Two important notes about this:
1) You have to supply Begin/Process/End (dont need all, but need at least 1)
2) It does not populate the variable as you’d expect it to, you have to pull it from $PSBoundParameters

Function New-DynamicParam {
param(
[string]
$Name,
[string[]]
$Options,
[switch]
$mandatory,
[string]
$SetName="__AllParameterSets",
[int]
$Position,
[switch]
$ValueFromPipelineByPropertyName,
[string]
$HelpMessage

)
    #param attributes   
    $ParamAttr = New-Object System.Management.Automation.ParameterAttribute
    $ParamAttr.ParameterSetName = $SetName
    if($mandatory){ $ParamAttr.Mandatory = $True }
    if($Position -ne $null){$ParamAttr.Position=$Position}
    if($ValueFromPipelineByPropertyName){$ParamAttr.ValueFromPipelineByPropertyName = $True}
    if($HelpMessage){$ParamAttr.HelpMessage = $HelpMessage}

    ##param validation set
    $ParamOptions = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $options

    $AttributeCollection = New-Object 'Collections.ObjectModel.Collection[System.Attribute]' 
    $AttributeCollection.Add($ParamAttr)
    $AttributeCollection.Add($ParamOptions)

    $Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter `
    -ArgumentList @($Name, [string], $AttributeCollection)
            
    $Dictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
    $Dictionary.Add($Name, $Parameter)
    $Dictionary
}

Here is an example of how you would use it.

function Show-Free
{
    [CmdletBinding()]
    Param()
    DynamicParam {
    New-DynamicParam -name Drive -options @(gwmi win32_volume |%{$_.driveletter}|sort) -Position 0 
    }
    begin{
    #have to manually populate
    $drive = $PSBoundParameters.drive
    }
    process{
    $vol = gwmi win32_volume -Filter "driveletter='$drive'"
    "{0:N2}% free on {1}" -f ($vol.Capacity / $vol.FreeSpace),$drive
    }
}

I’ve set this up to only work with strings, but if you look at the code there is actually nothing forcing you to use strings, just makes most sense for this.

Enjoy!

PowerShell: Working with Modules in ISE


Now that ISE isn’t crap with V3 I find that I dont really use any other editors. The one problem I have is when I work with modules, so I did a little screwing around and came up with this.


Function Import-CurrentFileAsModule

{

[cmdletbinding()]

param()

#get paths

$filePath = $psise.CurrentFile.FullPath

$folder = split-path $filePath

#save if not already saved

if($psise.CurrentFile.IsUntitled){Write-Error "Must save file first! Sorry didn't feel like implementing the dialog box!" -ErrorAction Stop}

if(-not $psise.CurrentFile.IsSaved){$psise.CurrentFile.Save()}

$global:WorkingModule = $null

#import the folder or the file if its standalone

try{
$psise.CurrentPowerShellTab.files | ?{-not $_.issaved} |? {$_.fullpath -like "$folder*"} | %{$_.save()}
$Global:WorkingModule = Import-Module $folder -Force -ErrorAction Stop -PassThru -Verbose:$false | select -ExpandProperty name
}

catch{$folderFailed = $true}

if($folderFailed)

{

try {Import-Module $filePath -Force -ErrorAction Stop -Verbose:$false}

catch{ write-error "Not a module file!" -ErrorAction Stop}

}

##post processing

if(Test-Path function:\PostModuleProcess)

{

Write-Verbose "Processing PostModuleProcess Function"

PostModuleProcess

}

else

{

Write-Verbose "--Create a PostModuleProcess function to excute code after import--"

}

Write-Verbose "Remove -verbose tag from last cmd in this file to stop verbose messaging"

}

 

Function Get-ModuleVariable{

param($Name)

if($global:workingmodule)

{

if($name)

{

&(gmo $global:workingmodule){Get-Variable -Name $args[0] -Scope script -ValueOnly} $name

}

else

{

&(gmo $global:workingmodule){Get-Variable -Scope script -ValueOnly}

}

}

}

Function Set-ModuleVariable{

[cmdletbinding()]

param(

[parameter(manditory=$true)]

$Name,

[parameter(manditory=$true)]

$Value)

if($global:workingmodule)

{

&(gmo $global:workingmodule){Set-Variable -Name $args[0] -Value $args[1] -Scope script} $Name $Value

}

}

$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add('Reload Module',{Import-CurrentFileAsModule -verbose},"F6")

,{Import-CurrentFileAsModule -verbose},"F6")

Just dump this in your profile or in a file and load it in your profile and F6 is ready to be used!

This create a couple of functions to allow you to look in to and edit module (script:  scope) variables, Get/Set-ModuleVariable and it will run PostModuleProcess function (if you create it) so that if you want to run any code after it loads you can easily do that.

You’ll notice in that last line that the Import-CurrentFileAsModule has the verbose tag on it, once you’re comfortable using it you can remove that.

Let me know what you think!

P.s. Thanks for the tips Jaykul!

opps! completely forgot that I should save all files in the module!

Using PowerShell Schedule Job to Reboot a Computer


I have to reboot a server tonight and well, I don’t want to have to VPN in to do it tonight. I know there is a Restart-Computer cmdlet which is cool but i need to schedule it, so i decided to use the PS Scehduled Job and came up with this


register-ScheduledJob -Name systemReboot -ScriptBlock {

Restart-Computer -ComputerName $server-Force -wait

Send-MailMessage -From jrich523@domain.com -To jrich523@domain.com -Subject "Rebooted" -SmtpServer smtp.domain.com

} -Trigger (New-JobTrigger -At "11/28/2012 6:00pm" -Once) -ScheduledJobOption (New-ScheduledJobOption -RunElevated) -Credential (Get-Credential)

Sorry, it’s one long line, but copy and paste it and you’ll be happy you took the time to figure it out.

Two important notes here are that I used the New-ScheduledJobOption to force it to run elevated, which is more often than not, needed.

The other part, which sort of threw me for a loop was that you have to specify credentials even if they are the creds you are currently logged in as. The reason for this is that if you don’t you wont have the creds in the job pass through to the script.

 

 

 

Enjoy!

PowerCLI New-VM or Set-VM: Could not load file or assembly ‘CryptoSupport.dll’ or one of its dependencies


I was trying to clone some VMs and apply an os config to it without any luck. I kept getting this error about CryptoSupport.dll and google wasnt helping me much.

After much digging I found that the CryptoSupport.dll is 32bit only and my ISE running in x64 wasnt too happy about loading it.

 

So, load 32 bit powershell.

 

Error looked something like this:

Could not load file or assembly ‘CryptoSupport.dll’ or one of its dependencies.  is not a valid Win32 application. (Exception from HRESULT: 0x800700C1)

PowerShell: Catching terminating and non-terminating errors in PS Jobs (Job Pattern)


–update: the receive-job has had -ErrorAction Stop added to it. Thanks Witquicked

Recently someone asked how you’d catch non-terminating errors returned by a ps job and how to figure out what that error is. I thought this would be way easier than it was.

I also realized there wasnt much info out there on this so here is the code I came up with.

$jobs=@()
$jobs += Start-Job -ScriptBlock {write-error "--non term msg here"}
$jobs += Start-Job -ScriptBlock {throw "--term!"}
$jobs += start-job -ScriptBlock {"im successful"}
$jobs += start-job -ScriptBlock {while($true){}}
Wait-Job $jobs -Timeout 5

foreach($job in $jobs)
{
	write-host "--------"	
	write-host "Job ID: $($job.id)"	
	switch($job.state){	
		"Completed" {		
			write-host "job ran to completion"			
			if($job.ChildJobs[0].Error)			
			{			
				write-host "non terminating errors"				
				write-host $job.ChildJobs[0].Error			
			}		
		}	
		"Failed"	
		{		
			write-host "Job Terminated with error "			
			write-host "Error message is:"			
			try{Receive-Job $job -ErrorAction Stop }catch{$_.exception.message}		
		}	
		"Running"		
		{		
			write-host "Job is still running, Forcefully stopping it"			
			$job.StopJob()		
		}
	}
}

This is also a pattern for handling PS jobs in general.

You certainly dont want to loop, which I see people doing a lot. There is also a timeout param which I would highly suggest using.

Boston PowerShell User Group


We have Tome Tanasovski coming to speak on tips and tricks!

http://www.powershellgroup.org/boston.ma

PowerShell – It’s All About Automation: Creating a DB and documenting it on SharePoint


I’m sure most of you know that I wear many hats. Sys admin, DBA, Storage admin etc etc. One of the things we’ve recently done here is to setup a central SQL Server because we noticed a lot of the servers had SQL or SQL Express that were not being backed up correctly or just not known about so to manage this we’ve moved them all to one DB cluster.

Because of this I now have to create DB’s on a more regular basis which involved a new DB, a SQL account and documenting it. You ask, why SQL accounts? It’s a slow process to get domain service accounts here, too much of a pain!

So there are 3 steps in this task.

  1. Create a DB
  2. Create the SQL Account (with random password)
  3. Insert data in to company WIKI (This resulted in my modifing a sharepoint wiki post)

I’m not a huge fan of using modules in scripts just to prevent there being any dependencies on them so we’ll need to load up a couple of .NET assemblies for this.


[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") [void][Reflection.Assembly]::LoadWithPartialName("System.Web")

SQL and the Web reference.

The System.Web is used to create the password. Sherif Talaat has a neat post on this.

Lets connect in and create that DB. The code assumes you’ve got a db name/instance (just a server name or server\instance name) stored in $dbinstance and it that $dbname has the name of the db you’d like to create.


$dbServer = new-object Microsoft.SqlServer.Management.Smo.Server ($dbInstance)

$db = new-object Microsoft.SqlServer.Management.Smo.Database $dbserver, $dbname $db.create();

$login = new-object Microsoft.sqlserver.management.smo.login $dbserver, $dbname $login.LoginType = 'SqlLogin' $login.DefaultDatabase = $dbname

$pass = [System.Web.Security.Membership]::GeneratePassword(10,0) $login.Create($pass) $db.SetOwner($dbname)

This makes a connection to the SQL server and then creates a new DB object and runs the Create method which injects it in to the SQL Server.

We then go about making a new SQL login and setting its default DB and login type. You’ll notice we use the DBName as the login name, this just makes it easier for tracking what belongs to what. It would be easy enough to change this.

Then we use the static GeneratePassword method in the web.secuirty.membership class to create a password for us and assign that to the login and set the login’s ownership of that DB.

Now we just take the username/db name and password and send it over to our wiki.

I wont repeat the code, since i’ve already documented it but you’ll want to do some string manipulation to make sure it ends up where you want it to. I use a simple template entry I search for in my wiki and then replace it with the template entry and new entry (ie, append)

The point here is that its really easy to make your life easier if you start thinking about some of the small tasks that need to be done and how easily (sometimes) they can be automated!

I’d love to hear about some of the tasks you’ve automated so drop a comment!

PowerShell: Remove a Drive Mount/Letter (More win32)


I’ve got a server with about 300 volumes on it and I obviously can’t use drive letters so I’ve got a bunch of mount points which works great the problem is that after the first reboot the automount kicked in and gave some of those volumes drive letters. Now all of my drive letters are taken up!

I don’t really want to use the storage manager to remove the 20 or so volume labels so I started to dig in to a PowerShell method and come to find out PSCX has a method but that was the only way. I’m not a huge fan of modules (didn’t want to deploy that to my server) so I figured if they can do it so can I.

With a little digging I found the win32 api that allows us to remove mount points (a drive letter is still a mount point) and this was easier than I would have imagined.

If you’ve read my Getting Started with Win32 API then this will make perfect sense to you.


Add-Type @"
using System;
using System.Runtime.InteropServices;

public class MountPoint
{
[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool DeleteVolumeMountPoint(string mountPoint);
}
"@

[MountPoint]::DeleteVolumeMountPoint("G:\")

Once I did that for each drive I needed to remove I loaded up DiskPart and typed AutoMount Disable which prevents drives from automounting at boot.
That’s all there is to it!

Inspect Methods in PowerShell


*EDIT: Code updated (Thanks Rob)

So the more you use PowerShell the less you want to leave it. When you end up with an object that you have no clue how to use you’ll likely use Get-Member to inspect it. Once you see what members it has and find a method you’d like to use you need to figure out what the params are.

$str = “hello”

$str | get-member

We can see that string has a bunch of methods and perhaps we want to use the compare but don’t know what the different overloads are so we can take a peek at them

$str.compare

You’ll notice there is () on that method call, and that’s what triggers PowerShell to show the different ways to use that method.

One problem I have with this is that some of them are so long you can’t see what belongs to what so here is my first attempt at showing it cleanly.

[string]::Compare.OverloadDefinitions -replace ‘\(‘,”`n`t ” -replace ‘,|\)’,”`n`t”

Thanks Rob for the shorter code.

Whats next? A function to do this.

function Format-Definition

{

[CmdletBinding()]

Param

(

[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)]

[System.Management.Automation.PSMethod] $method

)

$defs = $method.OverloadDefinitions

foreach($def in $defs)

{

if($def -like “*()”)

{Write-Host $def -f 6}

else

{

$splits = $def -split ‘\(|\)|,’

write-host $splits[0] -f 6

$splits[1..100] | %{$rtn = $_.trim() -split ” “

Write-host “`t$($rtn[0]) ” -F 8 -NoNewline

write-host $rtn[1]

}}}}

$str.compare | format-definition

Much better! Well, except for the colors, blame Rob for that :)

I don’t use the default color scheme so I figured I’d let you change that yourselves.

PowerShell: Enhance a CMDLET (Proxy/Wrapper)


I have this task to do, and well, I’m easily side tracked.

I find that I often times (especially with WMI calls) do a count and a Get-Member on the return just to see what I’m working with. So I thought, why not simplify that task?

function dwmi {
$rtn = iex "gwmi $([string]$args)"
$rtn | gm | ?{$_.name -notlike "__*"} | out-host
write-host "Count: $($rtn.count)"
return $rtn
}
$share = dwmi win32_share

This is a good example of where to use Invoke-Expression (IEX) as well as a good use of $args rather than named params. You could have this function use your own args as well, you’d just need to rip them out of $args before passing them to the CMDLET you are wrapping.

Just thought this was neat and worth sharing, hope you enjoy.

Follow

Get every new post delivered to your Inbox.

Join 115 other followers