PowerShell: Getting Started - Using the ISE and Creating Functions

PowerShell: Getting Started - Using the ISE and Creating Functions

Getting Started - Using the ISE and Creating Functions

PowerShell ISE

NOTE

The ISE is basically in maintenance mode now, and will likely only be getting security patches. Using Visual Studio Code is now the way to go.
Check out my post here on getting started with VS Code + PowerShell:

The ISE provides a way to use PowerShell more... powerfully. I generally prefer sticking to the shell if I'm using solid scripts and running random commands. However, once I get an idea for a script or start stringing different commands together, I turn to the ISE (or Visual Studio Code depending on what I'm doing). 

For the purpose of this getting started series, I will stick to using the ISE. The ISE was introduced with PowerShell 2.0, and follows relatively the same version outline as described in part one.

Features

  • IntelliSense (commands auto finish as you type them, and it shows available options)

  • Script debugging

  • Recent file list

  • Syntax highlighting

For a full list of features, check out this TechNet article.

Using the ISE

  • Launch the ISE by clicking the Start icon and searching for/typing "ISE".

  • You should see the following screen.

  • Go File -> Save As, and save this script as C:\PowerShell\part3.ps1 (Create the folder if it does not exist).

We are now ready to start using the ISE!

Customizing the ISE

As a preference I like to close the command pane on the right. 

You can also adjust the theme by going to Tools -> Options.

The script pane can be toggled in various ways by using the icons highlighted here:

Running Commands

Let's type in Get-Date, and then click the green arrow.

Here's the result:

The result is shown below in the script pane.

Now, let's add another command: 

Get-WMIObject -Class Win32_LogicalDisk

Highlight this command by clicking and dragging, and then click the icon next to the green arrow (smaller green arrow with paper behind it).

The result of this should show that the highlighted command ran. (it will display under the output of Get-Date)

The green arrow will run every command in the script, while the green arrow with paper behind it will only run the current selection. You can also use the shortcut keys F5 (to run the entire script) and F8 (to run just the selected text).

IntelliSense

Let's see what happens if you just type in Get-.

The ISE will show you a list of available command. Go ahead and choose Get-Alias.

On the same line as Get-Alias add a "-". 

You'll see that the ISE will display a list of available parameters and the type of input you can use when specifying an argument.

In this case it is showing that the Name parameter accepts an argument type of string array.

That means that either of these commands will work:

Get-Alias -Name ls

Get-Alias -Name ls,dir

The results of running each command should look similar to this:

The first command returns just the ls alias, as expected.

The second command will return both the ls, and dir output of the command. This is because PowerShell interprets the comma separated values as an array.

Let's move on to creating some functions!

Functions

As described in part 2 of my series, functions are:

a series of commands that are grouped together to perform a specific task (or at least, that's the best practice). You can write functions and scripts that mimic the behavior of Cmdlets (via providing error checking and comment based help).

Go ahead and clear out any commands we've put in the ISE so we can start with a clean slate.

Creating a Function

Let's create a function that works by gathering information about the current OS of your machine.

When naming functions it is best practice to use approved verbs, and use PowerShell's Verb-Noun format.

Type the following in the ISE:

function Get-OSInfo {

 Write-Host "This function will display OS information"

}

Go ahead and run this in the ISE using F5 or the green arrow to run the entire script.

Let's see the result.

It looks like it ran, but nothing happened!

That's because the function is now stored in this PowerShell session. You have to call it to use it. To call it, add this line to the script:

Get-OSInfo

Go ahead and run the full script again.

In the above example you can see that the function was called via the script, and that you also can type the function name in the script pane manually.

You can see that Write-Host was executed and the text was displayed.

Replace the code inside of the function with the following code:

$osInfo = Get-WmiObject -Class Win32_OperatingSystem

Return $osInfo

Run the script again.

You can see that the function returns the variable we set within it (which contains the OS information). The following information is displayed:

SystemDirectory : C:\WINDOWS\system32
Organization: Microsoft
BuildNumber : 10586
RegisteredUser: Microsoft
SerialNumber: x
Version : 10.0.10586

Let's modify the Get-OSInfo function to reflect the following code:

function Get-OSInfo {

$osInfo = Get-WmiObject -Class Win32_OperatingSystem

$versionText = Get-VersionText -version $osInfo.Version.Split('.')[0]

Write-Host "You're running $versionText"

}

Finally, add this function below the first one (Get-VersionText):

function Get-VersionText {
param($version)

Switch ($version) {

10{$versionText = 'Windows 10'}

6.3 {$versionText = 'Windows 8.1 or Server 2012 R2'}

6.2 {$versionText = 'Windows 8 or Server 2012'}

6.1 {$versionText = 'Windows 7 or Server 2008 R2'}

6.0 {$versionText = 'Windows Vista or Server 2008'}

5.2 {$versionText = 'Windows Server 2003/2003 R2'}

5.1 {$versionText = 'Windows XP'}

5.0 {$versionText = 'Windows 2000'}

}

Return $versionText

}

The script should now look like this:

Functions should do one thing, and that one thing well. The second function adds a switch statement. The switch statement evaluates a variable's value and performs actions depending on what it receives. It also accepts one parameter, which is $version

In the first function we use the .split() method on the version, and the [0] to select the first element in the split array.

You can see the result of that method if you run the following lines individually:

$osInfo = Get-WmiObject -Class Win32_OperatingSystem

$osInfo.Version.Split('.')[0]

That should return '10'. (if you're running Windows 10) 

If you'd like to see what the whole array looks like type

$osInfo.Version.Split('.')

You should see the following results (may vary depending on your version).

Let's get back to our script! Go ahead and run it!

If all goes well, it will display the returned text of the version you're running. 

What it does

At the bottom of the script the first function is called via the Get-OSInfo command. 

The function (Get-OSInfo) stores the WMI information for the  Win32_OperatingSystem class in the $osInfo variable.

Then we declare $versionText, which calls the function Get-VersionText  with the parameter -version and argument $osInfo.Version.Split('.')[0]

The Get-VersionText function then runs, and uses the Switch statement to determine how to set $versionText depending on what the value of $version is. This value is then returned into the $versionText variable called in the first function.

Finally, it uses Write-Host to display the text version of the version you're running that's stored in the $versionText variable.

After it executes, it will output "You're running Windows 10". (Or whichever version of Windows you're running).

 

That covers the basics of using functions in PowerShell. I will go over more advanced functions in part 4!

Homework

  • Use the Get-VersionText function with different numbers as the argument for the -version parameter.

  • Write your own functions! Maybe get some ideas from this post on Get-WMIObject.

  • Get a deeper understanding of the switch statement. How would you make it return a value in $versionText if none of the numbers specified are a match?

I hope you've enjoyed the series so far! As always, leave a comment if you have any feedback or questions!

-Ginger Ninja

[Back to top]

PowerShell Quick Tip: Basic use of the -f format operator

PowerShell Quick Tip: Basic use of the -f format operator

What the -f?

There are many ways to manipulate/build strings in PowerShell. One of my favorite ways is to use the -f format operator. When you use the operator, it evaluates everything to the right of -f as an array that starts at index value 0 (as all PowerShell arrays do). The values are comma separated.

Setup

For this demonstration I setup the following variables:

[array]$formatArray = @('you','values','them.')
$user               = (Get-ChildItem Env:\USERNAME).Value
$date               = Get-Date

Using the -f operator

Example one

"Your user name is {0}, and the time is [{1:HH}:{1:mm}:{1:ss}]" -f $user,$date

Returns

Your user name is Mike, and the time is [12:54:44]

Example two

Since it is indexing values as an array (starting at zero), you can actually feed it an array. Let's look at the values of the $formatArray array.

0 = you
1  = values
2 = them

Knowing that now, let's run this command:

"These {1} go where {0} place {2}" -f $formatArray

Returns

These values go where you place them.

Pretty cool, huh? There are multiple uses for this! Here is one example from a logging module I wrote. The context here is the user used the -addDate $true argument, and I need to append the date to the log name.

    if ($addDate) {
        
        if ($logName.Contains('.')) {
            
            $logName = $logName.SubString(0,$logName.LastIndexOf('.')) + "_{0:MM-dd-yy_HHmm}" -f (Get-Date) + $logName.Substring($logName.LastIndexOf('.'))
            
            Write-Verbose "Adding date to log file with an extension! New file name: $logName"
            Write-Verbose ""
           
        } else {
            
            $logName = $logName + "_{0:MM-dd-yy_HHmm}" -f (Get-Date)
            
            Write-Verbose "Adding date to log file. New file name: $logName"
            Write-Verbose ""
            
        }
         
    }

But wait... there's more!

As you can see in the examples above you can use the value with a colon and then specify more options. For these examples I used the options for getting specific values returned by Get-Date such as:

{1:HH}:{1:mm}:{1:ss}

This allows us to enumerate the values specifically for the 24 hour hour, minutes, and seconds returned by $date (which is where Get-Date is stored from the setup above).

There are other options you can use to to further format your strings. There are ones for different placements, spaces, and even returning different number values.  To check out the full details of what the -f format operator can do, check out these links!

As always, let me know if you have any questions or ideas in the comments section below!

-Ginger Ninja

PowerShell: Getting Started - Command Discovery

PowerShell: Getting Started - Command Discovery

Getting Started - Command Discovery

Welcome to my Getting Started with Windows PowerShell series!

Terminology Overview

This part may not be as exciting as the hands on stuff, but it is important to understand. Don't worry if you don't grasp a lot of it right away. It will all come together the more you use PowerShell. When your knowledge of it deepens you'll see more and more use cases for it yourself! We'll actually be building scripts, functions, and modules in subsequent posts! If you would like to skip to the hands-on stuff (and I don't blame you!) click here.

Cmdlets

Cmdlets are essentially snippets of .NET code or classes that perform a single function in PowerShell. The core functionality of PowerShell revolves around Cmdlets. Keep in mind that when you are creating functions, modules, or scripts that you aren't actually creating Cmdlets. If you'd like to look further into actually creating Cmdlets, check out this page. Please note that it is not actually writing PowerShell at that point.  It's .NET code that will interact, and provide functionality TO PowerShell.

Scripts

A PowerShell script is simply a file with a .ps1 extension that contains a series of PowerShell commands. The profile we created is actually an example of a PowerShell script.

Functions

A function in PowerShell is a series of commands that are grouped together to perform a specific task (or at least, that's the best practice). You can write functions and scripts that mimic the behavior of Cmdlets (via providing error checking and comment based help). Here's an example of a function I wrote in my post about using the LIFX API.

function Set-LightState { #Begin function Set-LightState
    [cmdletbinding()]
    param(
        [Parameter()]
        [ValidateSet('on','off')]
        [string]
        $state,
        [Parameter()]  
        [string]
        $color,
        [Parameter()]
        [ValidateRange(0.0,1)]
        [double]
        $brightness,
        [string]
        $selector
    )

    if ($lifxLight.Connected) {          

        $fullURL  = "$baseurl/$($lifxLight.id)/state"             
        $payload  = [PSCustomObject]@{

            power      = $state
            color      = $color
            brightness = $brightness

        }

        $payloadJson = $payload | ConvertTo-Json

        Write-Host "Attempting to Change light to:" -ForegroundColor $foregroundcolor
        Write-Host `t"State     :" $state
        Write-Host `t"Color     :" $color
        Write-Host `t"Brightness:" ($brightness * 100)"%" `n

        $stateReturn = Invoke-RestMethod -headers $headers -uri $fullURL -Method Put -Body $payloadJson -ContentType $acceptheader
    
        Write-Host "API status:" -ForegroundColor $foregroundcolor
        Write-Host `t"Light :" $stateReturn.results.label
        Write-Host `t"Status:" $stateReturn.results.status `n
    
    } else {
        
        $lightName = $lifxLight.label
        Write-Host "$lightName is offline or unreachable :("
        
    }    

} #End function Set-LightState

Here are some examples of that function in action.

Below you can see it being referenced in some code further on in the script.

Let's create a simple function to see how they work interactively.

function Get-RandomNameChaos {
param([string]$Name,
      [int]$Times
     )
    
    if ($Name) {
        $i = 1
        
        Do {

            Write-Host (Get-Random "`t","`n") [$i] [$Name] -foregroundColor (Get-Random -Minimum 1 -Maximum 15) -backgroundColor (Get-Random -Minimum 1 -Maximum 15)
        
            $i++
    
        } While ($i -lt ($times + 1)) 

    } else {

    Write-Host 'No name specified!' 
    
    }
}

This functions takes two arguments, -Name and -Time

You can get the function to work in PowerShell by:

  • Copy and pasting it in from above
  • Putting it in a script file and calling it within the script
  • Putting it in a module file and importing the module

I decided to paste it in and show you what happens as an example. I ran:

Get-RandomNameChaos -Name Mike -Times 3

Functions can take different arguments which are assigned to the parameters defined. 

This function we created has [int] next to the $Times parameter. This means that if you put anything other than an integer as a value, you will get an error message. 

Try running the function by calling it with different arguments. Maybe 100 for times, or even putting a non-integer value in just to see what it does.

Modules

Modules are PowerShell scripts with a .psm1 extension that contain a group of functions. Once you write a lot of functions you use over and over, it's generally a good time to create a module. You can import modules in PowerShell by using the Import-Module command. Here's an example of a logging module I created:

Above I imported my module to add logging functionality via specific functions I created. I then ran Get-Command -Module ninjalogging to get a list of commands it provides. 

Command structure

PowerShell Cmdlets (and well written functions) utilize the following format: Verb-Noun.

You can use the Get-Verb command to see a list of verbs that PowerShell uses and are officially approved. This can be handy when you're creating your own functions so you can use an approved verb that fits.

You can also run the following command which utilizes Get-CommandGroup-Object, and Sort-Object to list out the most commonly used verbs for all the PowerShell CMDlets you have access to.

Get-Command -CommandType Cmdlet | Group-Object Verb | Sort-Object Count -Descending | Format-Table -Autosize

If you have any questions about the above string of piped commands let me know in the comments, or try using Get-Help <command>.

As you can see a lot of the Cmdlets use only a few of the approved verbs. I find myself trying to use a lot of those verbs myself. There's Get, Set, and New just to name a few.

Aliases

There are aliases in PowerShell that make writing out one-liners and troubleshooting during a fire easier to manage. To see a list of aliases type Get-Alias.

You can even create you own aliases and use them in PowerShell. The alias I personally use the most is ls. To see what that Alias is linked to you can use:

Get-Alias -Name ls

ls is simply an alias for Get-ChildItem

Simple, but it provides a quick way to execute different Cmdlets and shorthand format.

You can check if an alias exists, and create your own with the following commands:

Get-Alias -name log
New-Alias -Name log -Value Out-LogFile -Description 'Link to Out-LogFile'
Get-Process | Out-String | log

The above example will not work for you as you do not have a function named Out-LogFile. What it does in my case is creates a text file logging the output of Get-Process. If you want to try to create your own alias, use Get-Process for the value and psax for the Name. Give it any Description you'd like.

You can then use:

Get-Alias -Name 'your alias name' 

to check on it. Here is the result for my example:

Command Discovery

The most straightforward command to use to discover commands is... Get-Command.

This command will display the commands as well as the command type, version, and its source.

You can narrow the scope of this command in various ways. One way is to specify the source:

Get-Command -Module Microsoft.PowerShell.Management

Let's say you wanted to discover all the commands available that had to do with processes. Let's try:

Get-Command -Name "*process*"

You could also run:

Get-Command | Where-Object {$_.Name -like "*process*"}

The $_. placeholder variable stands for the current element in the pipeline. In this case it would run through each line that is returned from Get-Command and look at the property Name ($_.Name). It then uses the comparison operator -like which allows us to specify wildcards. 

Either way the results are the same, and let's focus in on...

Get-Process

Here is the result of running Get-Process.

Formatting the results

You can also pipe the command to Format-List

Get-Process | Format-List

Here's an example of how to use Select-Object and Sort-Object. You can add more properties the the list if you want! Mess around with it to see what you can come up with that's useful to you. Here's what I ran:

Get-Process | Select-Object Name,CPU | Sort-Object CPU -Descending | Format-Table -AutoSize

Another way to output this information is to use Out-GridView. This can be a great way to get a visual of the returned items.

Get-Process | Out-GridView

In this instance I know that Get-Process is actually capable of displaying much more information. Let's try this command:

Get-Process | Select-Object * | Out-GridView

Well, that's a lot more information!

You can even add criteria to narrow it down.

I added one here (Use the [+ Add Criteria] button in the upper left) for VirtualMemorySize that islessthanorequalto 100000000.

Here's another one for Name contains chrome.

Exporting the Results

We can also use Get-Process with > and >>. These are very simple ways to output the results to a file.

Creates a new file every time (and will overwrite the file if it exists)
>> Appends the results to a file if it exists, or creates one if it doesn't

Let's look at:

Get-Process > processes.txt

Let's run the following command to see the contents of the file:

Start-Process .\processes.txt.

You can also use [TAB] completion. Try typing Start-Pro[TAB] .\pr[TAB]

Start-Process will take the input you give it and then run that with the associated application handler that's set. For example:

Start-Process www.google.com

The above command would open your default browser, and then navigate to www.google.com

Back to Get-Process!

If you were to use:

Get-Process >> processes.txt

It would append the results to the file that already exists.

Out-File

You can also use Out-File. I prefer this option as you have more control over what happens. You can specify the encoding, path, and other various options such as NoClobber.

Let's give it a try:

Get-Process | Out-File -FilePath C:\ninja\process.txt 

To append to this file using Out-File you would use:

Get-Process | Out-File -FilePath C:\ninja\process.txt -Append

Using Stop-Process with Get-Process

One of the handy things you can do with Get-Process is pipe it to Stop-Process. Just don't do this without specifying a process name, or you will very likely end up causing your computer to reboot!

This can, however, be a handy way to kill a hung process. Let's say you wanted to close any open notepad.exe instances. 

Get-Process notepad | Stop-Process

You wont see any information returned, and that usually means it found the process and stopped it as well. 

Profile Review

Below I have commented out the profile we created in part one with information on what each line is doing. 

#Set the variable $foregroundColor to be white. Global means it will be set in all scopes. 
$global:foregroundColor = 'white'

#Store the results of Get-Date in the $time variable
$time = Get-Date

#Store the current version in the $psVersion variable
$psVersion= $host.Version.Major

#Store the current user and computer in each variable 
$curUser= (Get-ChildItem Env:\USERNAME).Value
$curComp= (Get-ChildItem Env:\COMPUTERNAME).Value

#Write-Host writes to the host that's running the script. In this case, very likley your console!
#The following lines takes the variables above and uses them in the output in various ways.
Write-Host "Greetings, $curUser!" -foregroundColor $foregroundColor
Write-Host "It is: $($time.ToLongDateString())"
Write-Host "You're running PowerShell version: $psVersion" -foregroundColor Green
Write-Host "Your computer name is: $curComp" -foregroundColor Green
Write-Host "Happy scripting!" `n

#This replaces the default prompt function with our own!
function Prompt {

#We get the time again as this function runs every single time you hit enter.
$curtime = Get-Date

#Writing to the host, this time with -noNewLine.
#-NoNewLine means it will keep writing to the same line. 
#This allows us to use multiple colors on the same line.
Write-Host -NoNewLine "p" -foregroundColor $foregroundColor
Write-Host -NoNewLine "$" -foregroundColor Green
Write-Host -NoNewLine "[" -foregroundColor Yellow

#This line uses a string format operator (-f) and takes the results from Get-Date and formats them on the left.
#I'll be detailing more about how that works in a later post!

Write-Host -NoNewLine ("{0:HH}:{0:mm}:{0:ss}" -f (Get-Date)) -foregroundColor $foregroundColor

#Closing off the prompt 
Write-Host -NoNewLine "]" -foregroundColor Yellow
Write-Host -NoNewLine ">" -foregroundColor Red

#This variable controls the PowerShell window title. We set it to the current path everytime this function runs.
$host.UI.RawUI.WindowTitle = "PS >> User: $curUser >> Current DIR: $((Get-Location).Path)"

#This next line is needed so we can override the default prompt with our Write-Host above!
Return " "

}

Homework

  • Write your own profile using the one I gave you as a template for ideas.
  • Customize the Get-RandomNameChaos function some more!
  • Figure out just what the heck "{0}" -f (Get-Date) does and how it works. (Hint: String format operators)
  • Get Get-Process | Out-GridView to show you only properties you care about.
  • Learn what a calculated property is.

In part 3, we go over the ISE and creating functions!

-Ginger Ninja

[Back to top]

PowerShell: Getting Started - Customizing Your Environment!

PowerShell: Getting Started - Customizing Your Environment!

Getting Started - Basics and Environment Customization

Welcome to my Getting Started with Windows PowerShell series!

We will be exploring:

Update 4/24/19: Look for some of these series pages to get revamped. PowerShell Core is making waves! If you haven’t checked it out, you should.

Looking to get started with Visual Studio Code + PowerShell? Check out this post:

Just What is PowerShell?

PowerShell is a framework provided by Microsoft that provides a platform for automation, general scripting, and well just about anything you can imagine doing with it. It's based on .NET, and has hooks into pretty much anything Windows can do. There are a lot of new things introduced with PowerShell, and it is ever evolving. You can also still use old commands you're familiar with like ping, but you have much more powerful options at your finger tips (like Test-Connection).  If you'd like a deeper dive into what PowerShell is, check out Microsoft's Scripting Guy's post, here.

PowerShell is an object-oriented programming language with a ton of features. Everything we work with PowerShell is an object one way or another. What this means is we can get information from it via properties, and take actions with it via methods.

You can use PowerShell in two distinct ways.

PowerShell also makes it easy to create your own custom objects, more on that in another post!

PowerShell Versions

Here's the breakdown: 

  • Windows 7 comes with Windows PowerShell version 2.0 installed by default. If you're still running Windows 7, and want to try out the latest version of PowerShell (currently 5.0), you'll need to install the Windows Management Framework update

  • Windows 8 versioning is a bit strange

  • You can run PowerShell 2.0-3.0

    • You cannot run PowerShell 4.0+

  • Windows 8.1 let's you run version 5.0

  • Windows 10 comes with PowerShell version 5.0 installed by default, and if you're all updated (which it is hard not to be with how Windows 10 updates work...) version 5.1+ will be available

If for some reason you're using a machine running Windows XP, PowerShell 2.0 will work with this download.

PowerShell Core

PowerShell core heralds the open sourcing of PowerShell, check it out on Github! You can even run it on your Raspberry Pi.

How can I check what version I have?

Well, first you'll have to open PowerShell

  1. Hit the Windows key or click the Start icon/button.

  2. Type in 'PowerShell', and select 'Windows PowerShell'

I recommend pinning PowerShell to your taskbar as well. This makes it really easy to launch.

PowerShell stores the version in a special variable dedicated to host information. This variable is aptly named $host. To see the value of this variable, type $host into your console and press enter. You should see a similar result to this:

If you simply wanted to return the latest version, try typing $host.Version to display the Version property only.

Finally, if you want to just return the Major Version, use $host.Version.Major.

For a more detailed write up on versions and supported OSs, see this post at 4sysops.

How PowerShell Works

PowerShell works by executing commands, and then provides you a way to interpreting the results. Everything in PowerShell either is or becomes an object in one way or another. Think of an object in this instance as something you can take action on via methods, and get information from via properties.

Let's learn some more basics before we go about customizing our environment. Don't worry too much about grasping terminology! With PowerShell especially, learning by doing is where it's at.

'Hello World'

Even the simplest thing in PowerShell, such as 'Hello World',  becomes an object you can take action on. Go ahead and type the following command in PowerShell:

'hello world'.Length

The above example should return 11, as seen below.

Since 'Hello World' is an object, we can pipe it via "|" to Get-Member to see what we can do with it. Piping in PowerShell is the act of adding the "|" character to take the results of the input before it, and pass that over to the next command. Let's go ahead and run this:

'Hello World' | Get-Member

You should see the following:

You can see that the object Type is of System.String, and below that the various methods and properties. To use them, simply add a dot after 'Hello World' and specify the one you'd like to use. For instance, I wonder what ToUpper does. Let's see!

'Hello World'.ToUpper

Hmm... that looks a little weird. That's because to execute the method, you need to put a pair of parentheses after it. Sometimes you can include different values in the parentheses to include overload options. What we're seeing here is the definition of those options for .ToUpper(). For this example we can just use:

'Hello World'.ToUpper()

Get-Member will likely be one of the handiest cmdlets you will use. It lets you know what properties and methods the object contains.

Now that we've covered some basics, let's get back to checking out...

Ping vs Test-Connection

Let's ping google.com via PowerShell.

Ping Google.com

Alright, pretty standard! Let's see what Test-Connection Google.com does.

Test-Connection Google.com

Now that looks a little bit different. So what's so special about Test-Connection? To see what else Test-Connection can do and the options it provides, use:

Get-Help Test-Connection

Notice under REMARKS it states that I don't have the help files stored on my computer. To remedy this, right click your PowerShell icon on the taskbar, and go to Run as Administrator. Then use this command:

Update-Help

Now let's see the help for Test-Connection again!

Get-Help Test-Connection

Under the SYNTAX portion you can see that Test-Connection accepts the -ComputerName parameter. This is the one that Google.com was placed into by default. It then specifies what the input is expected to be. This parameter accepts a string, and string array. That is what the [] next to string means. Think of an array as a collection of values.

To see examples of how to run Test-Connection, type:

Get-Help Test-Connection -Examples

Variables

Let's take advantage of the fact that Test-Connection's -ComputerName parameter can accept a string array. To do this, we'll need to create a variable and add some values to it. The best way to create a string array is to use this command:

[System.Collections.ArrayList]$testArray = @()

This above code will create an empty array in the variable $testArray. Think of a variable as a container of objects.

Let's add some hosts to this array that we'll want to use with Test-Connection

$testArray.Add('192.168.1.1')
$testArray.Add('google.com')
$testArray.Add('qwertyuiop.asdf')
 

Arrays in PowerShell always start with 0, and when we use the .Add method on this array you can see it outputs the index of the element(value) we are adding. To add an element without seeing that, simply pipe $testArray.Add('yahoo.com') to Out-Null.

$testArray.Add('yahoo.com') | Out-Null

You can see it did not return the index number this time. To display the values in the array, type:

$testArray

OK! Now that we have our array setup, let's use:

Test-Connection -ComputerName $testArray

You can even use Test-Connection with conditional logic. 

if (Test-Connection Google.com) {Write-Host "Success!"} 

Since Test-Connection Google.com returned $true, it proceeds to perform the command in the script block {}.

I wonder what happens if you replace Google.com with 'qwertyuiop.asdf'...

Alright! Now that we've gone through some more of the basic concepts, it's time to...

Customize Your Environment

Open up your PowerShell console and Right Click the title bar. 

  1. Select Properties.

  2. Select the Font tab to adjust the font.

  3. Select the Colors tab to set the colors you want.

Customizing your profile

PowerShell uses profile files to automatically load a script when you start the PowerShell console.

Let's take a look at the file PowerShell uses for your current user profile on all hosts (meaning the ISE and console). We'll get into the different host types in a different post. The special variable we'll want to look at is $profile, and we'll want to see the CurrentUserAllHosts property.

$profile.CurrentUserAllHosts

It looks like the generic Dell account (my way to have a fresh instance of PowerShell) I'm using would have the profile stored in:

C:\Users\Dell\Documents\WindowsPowerShell\profile.ps1

Since the folder and file do not exist, let's use the New-Item cmdlet to create each. Be sure to change the values to match what your result was from the $profile.CurrentUserAllHosts value. Note: the file will still be profile.ps1, and only the user name should change.

New-Item -Path C:\Users\Dell\Documents\ -ItemType Directory -Name WindowsPowerShell
New-Item -Path C:\Users\Dell\Documents\WindowsPowerShell\ -ItemType File -Name profile.ps1

To auto-magically do this, you can use the following commands:

New-Item -Path "$((Get-ChildItem ENV:\UserProfile).Value)\Documents\" -ItemType Directory -Name WindowsPowerShell
New-Item -Path "$((Get-ChildItem ENV:\UserProfile).Value)\Documents\WindowsPowerShell" -ItemType File -Name profile.ps1

Now you should be able to use the Start-Process cmdlet(which opens a file with the associated handler in Windows automatically) to open and edit the profile file.

Start-Process $profile.CurrentUserAllHosts

You should now have a blank text file open with profile.ps1 as the name in the upper left.

Let's add the following code to the profile.ps1 file:
I will detail what this code does in the next post!

$foregroundColor = 'white'
$time = Get-Date
$psVersion= $host.Version.Major
$curUser= (Get-ChildItem Env:\USERNAME).Value
$curComp= (Get-ChildItem Env:\COMPUTERNAME).Value

Write-Host "Greetings, $curUser!" -foregroundColor $foregroundColor
Write-Host "It is: $($time.ToLongDateString())"
Write-Host "You're running PowerShell version: $psVersion" -foregroundColor Green
Write-Host "Your computer name is: $curComp" -foregroundColor Green
Write-Host "Happy scripting!" `n

function Prompt {

$curtime = Get-Date

Write-Host -NoNewLine "p" -foregroundColor $foregroundColor
Write-Host -NoNewLine "$" -foregroundColor Green
Write-Host -NoNewLine "[" -foregroundColor Yellow
Write-Host -NoNewLine ("{0}" -f (Get-Date)) -foregroundColor $foregroundColor
Write-Host -NoNewLine "]" -foregroundColor Yellow
Write-Host -NoNewLine ">" -foregroundColor Red

$host.UI.RawUI.WindowTitle = "PS >> User: $curUser >> Current DIR: $((Get-Location).Path)"

Return " "

}

Once you've added the content, save profile.ps1.
Now close and re-open your PowerShell console.

It should now look similar to this:

Each time you type a command the function named prompt executes and changes both the prompt (to include the current time), and the Window Title (to include the current user and directory).

In the next post I will be going over command discovery and formatting results. 

Homework

  • Try to figure out exactly how the string format operator works in the prompt function to format the time.

  • Use Get-Command to discover more PowerShell commands.

  • Find some commands to run (maybe Get-Date)? and Pipe them to Get-Member to see what properties and methods they contain.

  • Declare a variable and use that variable to use the methods and display the properties available. Hint: $time = Get-Date.

  • Further customize your profile to change up your prompt and title!

Let me know if you have any questions! Feedback is always appreciated.

-Ginger Ninja

[Back to top]

PowerShell: List out your PuTTY sessions

PowerShell: List out your PuTTY sessions

I wanted a way to export my PuTTY sessions as I was about to reformat my machine. You could export the registry keys and keep them, but I wanted something more visual as well. So I turned to my dear old trusty friend, PowerShell.

What you'll need

  • PowerShell
  • PuTTY installed with at least one session
  • The ability to overlook Write-Host as an always bad thing
    • It's only mostly always bad, ok? But I wanted some formatting and colors
      • I'm only judging myself a little bit.

Let's jump into it! (The code)

$sessions          = Get-ChildItem 'HKCU:\Software\SimonTatham\PuTTY\Sessions'
[array]$properties = @('HostName','TerminalType','TerminalSpeed')

Write-Host 'Listing PuTTy sessions' -ForegroundColor Green `n
foreach ($session in $sessions) {
    
    $name       = ($session.Name.Substring($session.Name.LastIndexOf("\") + 1)).Replace("%20"," ")
    Write-Host $name -ForegroundColor White
    
    Foreach ($property in $properties) {
                
        Write-Host -NoNewLine `t $property":" -ForegroundColor Green
        Write-Host $session.GetValue($property)
        
        }

    Write-Host `n
    
}

There isn't too much to this one, but it's also the tip of the iceberg. I chose to make it more human readable as if you simply did this:

Get-ChildItem HKCU:\Software\SimonTatham\PuTTY\Sessions

The output is all of the key's properties:

So what I did is cherry picked some of the values I wanted to display.

[array]$properties = @('HostName','TerminalType','TerminalSpeed')

I then used a foreach loop to go through each property listed and display it for that session.

Foreach ($property in $properties) {

Write-Host -NoNewLine `t $property":" -ForegroundColor Green
Write-Host $session.GetValue($property)

}

The execution

My PuTTY session is pretty lonely, but just one will work!

PowerShell code execution:

I named my script Backup-Putty.ps1 as I initially made it as a way to get a quick human readable copy and paste of the sessions I cared about. With more effort you could easily make this script output the list into a CSV and store it that way.

PowerShell Quick Tip: Use a text file to omit values in an array

PowerShell Quick Tip: Use a text file to omit values in an array

After making the Lun Check Script I had an issue come up when somebody decommissioned a server. The LUNs that were mapped needed to be set offline for a week, then deleted. Since the script would see if ANY LUNs (sans ones with test in the name) were offline, this triggered an alert. 

To mitigate this problem, I added a text file that the script imports and uses to exclude any LUNs with the names included in the file in their path.

To demonstrate this, here is an example text file contents:

Exclude.txt File contents:
test
al*
delta
th*

$processThis would be the full list of LUNs, for example.

$exclude = Get-Content C:\exclude.txt
$processThis = @('test','delta','alpha','this','that','and','the','other','thing')

We can try to use -notin to see how that works when excluding the contents of Exclude.txt...

$processThis | Where-Object {$_ -notin $exclude}
alpha
this
that
and
the
other
thing

Well, that worked for 'test' and 'delta', but not the wildcard entries! I need to accommodate wildcards per my team's request.  To do this I used a ForEach-Object with Where-Object nested inside of it.

$omit = $exclude | ForEach-Object{$no = $_;$processthis | Where-Object{$_ -like $no}}

Now $omit will contain the full list of items to omit.

$omit
test
alpha
delta
this
that
the
thing

And we can finally use Where-Object with -notin to finish it up.

$processThis | Where-Object {$_ -notin $omit}
and
other

I hope you found this helpful! If you know of a better way, or have any questions, let me know!

-Ginger Ninja

PowerShell: NetApp LUN monitoring with a PRTG custom sensor

PowerShell: NetApp LUN monitoring with a PRTG custom sensor

When a LUN goes offline, bad things tend to happen on the host using it. It is good to know sooner rather than later if that happens. I was looking for a way to do this and what made the most sense was using PowerShell and PRTG.

Requirements

  • PRTG installation (you can get it free for up to 100 sensors!)
    • While you don't NEED PRTG that is how this script reports back. You'll need to change what the actions are for offline LUNs if you want to do other things.
  • ONTap PowerShell toolkit
    • This will need to be installed where you are using the script.
  • A user account on the NetApp(s) that the script can leverage.

Setup

You'll need to setup:

  • The module full path for the ONTap toolkit (if different).
  • The password setup section that is commented out.
  • The username ($user) you are going to use to connect to the NetApp(s).
  • The array ($netAppControllers) of your controllers. For 7-mode simply include the controller name, for CDOT add -c after the name so the script knows which is which.
  • The $exclude variable file path. (It is best to set this up even if it is a blank text file).
[cmdletbinding()]
param(
    [string]
    $Controller,
    [string]
    $action
)
#Import the OnTap module manually using the following path.
Import-Module 'C:\Program Files (x86)\NetApp\NetApp PowerShell Toolkit\Modules\DataONTAP\dataontap.psd1'

#Encrypted password hash used to get the password securely.
$File = "D:\PRTG Network Monitor\Custom Sensors\EXEXML\lunpass.txt"

#Setup password (Do this one time or whenever you change the password!:
#[Byte[]] $key = (1..16)
#$Password = "P@ssword1" | ConvertTo-SecureString -AsPlainText -Force
#$Password | ConvertFrom-SecureString -key $key | Out-File $File

#User account used to get NetApp information.
$user                     = 'your NetApp PowerShell user account'

#Key used to decrypt password
[Byte[]] $key = (1..16)

#Import the password from the file using the key.
$pass = Get-Content $File | ConvertTo-SecureString -Key $key

#Create the credential used to connect to each NetApp controller
$netAppCredential         = New-Object System.Management.Automation.PSCredential($user,$Pass)

#Set the variable which contains all of your controllers (add -c for CDOT)
$global:NetAppControllers = @('netapp1','netapp2','netapp3-c')

#LUNs to exclude
$exclude = Get-Content 'D:\PRTG Network Monitor\Custom Sensors\EXEXML\exclude.txt'

#This sets the variable of $stopTxt to null (which is set later if there is a failed LUN)
$stopTxt = $null

The functions and switch

#This function connects to the 7-Mode controllers the way they need to be connected to, as well as C-DOT.
function Connect-NetAppController {

    
    if (($foundController).Split("-")[1] -like "*c*") { 

        $nController = $foundController.Split("-")[0]
        

        
        if (Test-Connection $nController -Count 1 -Quiet) {

       
        Connect-NcController $nController -Credential $netAppCredential | Out-Null

    
        } Else {
            
           #Write-Host `n"Unable to ping: " $nController
            
        }

    } else {

        $7Controller = $foundController

        if (Test-Connection $foundController -Count 1 -Quiet) {
        
        $7Controller = $foundController

        Connect-NaController $foundController -Credential $netAppCredential | Out-Null

       
         } Else {
            
            #Write-Host `n"Unable to ping: " $7Controller
            
        }
    } 
}

#This functions takes the object created in the switch and output the information properly for PRTG to interpret
Function Display-PRTGText {
[cmdletbinding()]
param([array]
      $PRTGObject
)   
    
    $header = '<prtg>'
    $footer = '</prtg>'

    Write-Output $header
    
    foreach ($lun in $PRTGObject) {

    if ($lun.NameOff) {
        
        $stopTxt    = $true
        $offlineLun += $lun.NameOff 
        
    }    

    if ($lun.Exclusions) {
        
        $exclude    = $true
        $excludeTxt = $lun.Exclusions
        
    }
    
    Write-Output "
    
    <result>
    <channel>Total LUNs ($($lun.Name))</channel>
    <value>$($lun.TotalLuns)</value>
    </result>
    
    <result>
    <channel>Offline LUNs ($($lun.Name))</channel>
    <value>$($lun.OfflineLuns)</value>
    </result>
    
    "   
    }

    if ($stopTxt) {
        
        Write-Output "
        
        <text>Offline LUNs: $offlineLun</text>
        
        "
    } elseif (!$stopTxt -and $exclude) {
        
        Write-Output "
        
        <text>Exclusions: $excludeTxt</text>
        
        "
    }

    Write-Output $footer    
    
}


#This switch takes action based on what you specify in the parameter. Right now there is only one: luncheck
Switch ($action) {
    
    'luncheck' {
          
        if ($controller -eq 'all') { 
    
            [array]$cModeControllers = $NetAppControllers
     
        } else {
            
            [array]$cmodecontrollers = $controller

        }
        
        #Set/Clear this array to be used at the NetApp Object
        [array]$netAppObject = $null
        
        foreach ($array in $cModeControllers) {
            $foundcontroller = $array
            $controller      = $array   
            $numLuns         = 0
            $numOn           = 0
            $numOff          = 0
            $offline         = $null
            $online          = $null
            $luns            = $null
            $omit            = $null
            $newLuns         = $null
            $text            = $null
            
            #if it is a CDOT controllers (I add -c in the name to segement them out)
            if (($foundController).Split("-")[1] -like "*c*") {

                Connect-NetAppController 
                
                $luns    = Get-NCLun | Where-Object{$_.Path -notlike "*test*"}

                if ($exclude.count -gt 0) {

                    $omit = $exclude | ForEach-Object{$no = $_;$luns | Where-Object{$_.path -like $no}}
                    $luns = $luns | Where-Object{$_.Path -notin $omit.Path}
                
                }
                
                $numLuns = $luns.count
                $offline = $luns | Where-Object{$_.Online -ne $true}
                $online  = $luns | Where-Object{$_.Online -eq $true}
                $numOff  = $offline.count
                $numOn   = $online.count       
                
                $netAppProp     = @{Name=$controller}
                $netAppProp     += @{TotalLuns=$numLuns}
                $netAppProp     += @{OfflineLuns=$numOff}
                $netAppProp     += @{OnlineLuns=$numOn}
                $netAppProp     += @{Exclusions=$exclude}
                
                if ($numOff -gt 0) {
                    
                    foreach ($off in $offline) {[array]$text += 'Lun: ' + $off.path + ' VServer: ' + $off.vserver}                
                    $netAppProp += @{NameOff=$text}
                    
                }

                $naO             = New-Object PSObject -Property $netAppProp
                $netAppObject   += $naO       
                                      
            #Else they are 7-mode so use 7-mode commands            
            } else {

                Connect-NetAppController 
                
                $luns    = Get-NALun | Where-Object{$_.Path -notlike "*test*"}  
                
                if ($exclude.count -gt 0) {
                    
                    $omit = $exclude | ForEach-Object{$no = $_;$luns | Where-Object{$_.path -like $no}}
                    $luns = $luns | Where-Object{$_.Path -notin $omit.Path}
                
                }
                
                $numLuns = $luns.count
                $offline = $luns | Where-Object{$_.Online -ne $true}
                $online  = $luns | Where-Object{$_.Online -eq $true}
                $numOff  = $offline.count
                $numOn   = $online.count
                
                $netAppProp     = @{Name=$controller}
                $netAppProp     += @{TotalLuns=$numLuns}
                $netAppProp     += @{OfflineLuns=$numOff}
                $netAppProp     += @{OnlineLuns=$numOn} 
                $netAppProp     += @{Exclusions=$exclude}
                              
                if ($numOff -gt 0) {
                    
                    foreach ($off in $offline) {[array]$text += 'Lun: ' + $off.path}           
                    $netAppProp += @{NameOff=$text}
                    
                }
                
                $naO             = New-Object PSObject -Property $netAppProp
                $netAppObject   += $naO    

            }      
        
        }
   
        Display-PRTGText $netAppObject
    
    }
}

The code lets you run the script specifying "all" controllers (which will use the array defined at the beginning of the script), or a controller of your choice.

The function Display-PRTGText takes the $netAppObject object that is built below in the switch option luncheck. It first displays the PRTG header, the LUN information, and finally the PRTG footer. 

The switch looks for the action you'd like to take. Right now there is only one, "luncheck". You can add more for different sensor types to check different things based on what you pass to -action.

Examples

To test the script, run it as follows:

.\Lun-Check.ps1 -Controller yourcontroller-c -action luncheck

The above code will execute and if all goes well it will return results with opening and closing <prtg> tags.

If there are any offline, they will be returned into the PRTG sensor via the <text> tag. 

Exclusions

You can exclude certain LUNs from being checked. If you have decommissioned a server or are testing anything that involves setting a LUN's state to be offline this can be handy. Add entries to exclude.txt line by line. You can include either the full LUN path, or anything in the path name with wildcards around it.  Please note that there is also a line that omits any LUN with "test" in the name. To change this, remove that if line.

Examples:

  • /vol/finance1/lun1
  • *finance*

The first example will omit just that one LUN, whereas the second example will omit any LUN with finance in the path. Any exclusions you do add will be noted in the sensor's message as text if no LUNs are currently offline.

Once you have verified you received the results from the script as needed, we can now perform the sensor setup in PRTG.

PRTG Setup

You'll want to perform the following steps in PRTG:

  1. Ensure you have the script in your EXEXML custom sensor folder. Example: "D:\PRTG Network Monitor\Custom Sensors\EXEXML\lun-check.ps1"
  2. Create an "EXE/Script Advanced" sensor on a device of your choice.
  3. Select the lun-check.ps1 script in the drop down.
  4. Set the following parameters: -controller all -action luncheck
  5. Modify the timeout of the script to 180 seconds.

If you do not want to use the array with all the NetApp(s) change 'all' to the name of a controller. If it is a CDOT controller, be sure to add -c to the name.

You should now see channels for total and offline LUNs.

I have it setup to show the sensor as down if there are any LUNs offline. To do this, be sure to change the channel limits accordingly. It will return the path of the LUN and the VServer it is on (CDOT) or just the path for 7-Mode. This is displayed in the sensor's message as text.

 

And finally, you'll want to tweak the notification settings of the sensor if for some reason you want it different than the parent's settings.

Full Code

[cmdletbinding()]
param(
    [string]
    $Controller,
    [string]
    $action
)
#Import the OnTap module manually using the following path.
Import-Module 'C:\Program Files (x86)\NetApp\NetApp PowerShell Toolkit\Modules\DataONTAP\dataontap.psd1'

#Encrypted password hash used to get the password securely.
$File = "D:\PRTG Network Monitor\Custom Sensors\EXEXML\lunpass.txt"

#Setup password (Do this one time or whenever you change the password!:
#[Byte[]] $key = (1..16)
#$Password = "P@ssword1" | ConvertTo-SecureString -AsPlainText -Force
#$Password | ConvertFrom-SecureString -key $key | Out-File $File

#User account used to get NetApp information.
$user                     = 'your NetApp PowerShell user account'

#Key used to decrypt password
[Byte[]] $key = (1..16)

#Import the password from the file using the key.
$pass = Get-Content $File | ConvertTo-SecureString -Key $key

#Create the credential used to connect to each NetApp controller
$netAppCredential         = New-Object System.Management.Automation.PSCredential($user,$Pass)

#Set the variable which contains all of your controllers (add -c for CDOT)
$global:NetAppControllers = @('netapp1','netapp2','netapp3-c')

#LUNs to exclude
$exclude = Get-Content 'D:\PRTG Network Monitor\Custom Sensors\EXEXML\exclude.txt'

#This sets the variable of $stopTxt to null (which is set later if there is a failed LUN)
$stopTxt = $null

#This function connects to the 7-Mode controllers the way they need to be connected to, as well as C-DOT.
function Connect-NetAppController {

    
    if (($foundController).Split("-")[1] -like "*c*") { 

        $nController = $foundController.Split("-")[0]
        

        
        if (Test-Connection $nController -Count 1 -Quiet) {

       
        Connect-NcController $nController -Credential $netAppCredential | Out-Null

    
        } Else {
            
           #Write-Host `n"Unable to ping: " $nController
            
        }

    } else {

        $7Controller = $foundController

        if (Test-Connection $foundController -Count 1 -Quiet) {
        
        $7Controller = $foundController

        Connect-NaController $foundController -Credential $netAppCredential | Out-Null

       
         } Else {
            
            #Write-Host `n"Unable to ping: " $7Controller
            
        }
    } 
}

#This functions takes the object created in the switch and output the information properly for PRTG to interpret
Function Display-PRTGText {
[cmdletbinding()]
param([array]
      $PRTGObject
)   
    
    $header = '<prtg>'
    $footer = '</prtg>'

    Write-Output $header
    
    foreach ($lun in $PRTGObject) {

    if ($lun.NameOff) {
        
        $stopTxt    = $true
        $offlineLun += $lun.NameOff 
        
    }    

    if ($lun.Exclusions) {
        
        $exclude    = $true
        $excludeTxt = $lun.Exclusions
        
    }
    
    Write-Output "
    
    <result>
    <channel>Total LUNs ($($lun.Name))</channel>
    <value>$($lun.TotalLuns)</value>
    </result>
    
    <result>
    <channel>Offline LUNs ($($lun.Name))</channel>
    <value>$($lun.OfflineLuns)</value>
    </result>
    
    "   
    }

    if ($stopTxt) {
        
        Write-Output "
        
        <text>Offline LUNs: $offlineLun</text>
        
        "
    } elseif (!$stopTxt -and $exclude) {
        
        Write-Output "
        
        <text>Exclusions: $excludeTxt</text>
        
        "
    }

    Write-Output $footer    
    
}


#This switch takes action based on what you specify in the parameter. Right now there is only one: luncheck
Switch ($action) {
    
    'luncheck' {
          
        if ($controller -eq 'all') { 
    
            [array]$cModeControllers = $NetAppControllers
     
        } else {
            
            [array]$cmodecontrollers = $controller

        }
        
        #Set/Clear this array to be used at the NetApp Object
        [array]$netAppObject = $null
        
        foreach ($array in $cModeControllers) {
            $foundcontroller = $array
            $controller      = $array   
            $numLuns         = 0
            $numOn           = 0
            $numOff          = 0
            $offline         = $null
            $online          = $null
            $luns            = $null
            $omit            = $null
            $newLuns         = $null
            $text            = $null
            
            #if it is a CDOT controllers (I add -c in the name to segement them out)
            if (($foundController).Split("-")[1] -like "*c*") {

                Connect-NetAppController 
                
                $luns    = Get-NCLun | Where-Object{$_.Path -notlike "*test*"}

                if ($exclude.count -gt 0) {

                    $omit = $exclude | ForEach-Object{$no = $_;$luns | Where-Object{$_.path -like $no}}
                    $luns = $luns | Where-Object{$_.Path -notin $omit.Path}
                
                }
                
                $numLuns = $luns.count
                $offline = $luns | Where-Object{$_.Online -ne $true}
                $online  = $luns | Where-Object{$_.Online -eq $true}
                $numOff  = $offline.count
                $numOn   = $online.count       
                
                $netAppProp     = @{Name=$controller}
                $netAppProp     += @{TotalLuns=$numLuns}
                $netAppProp     += @{OfflineLuns=$numOff}
                $netAppProp     += @{OnlineLuns=$numOn}
                $netAppProp     += @{Exclusions=$exclude}
                
                if ($numOff -gt 0) {
                    
                    foreach ($off in $offline) {[array]$text += 'Lun: ' + $off.path + ' VServer: ' + $off.vserver}                
                    $netAppProp += @{NameOff=$text}
                    
                }

                $naO             = New-Object PSObject -Property $netAppProp
                $netAppObject   += $naO       
                                      
            #Else they are 7-mode so use 7-mode commands            
            } else {

                Connect-NetAppController 
                
                $luns    = Get-NALun | Where-Object{$_.Path -notlike "*test*"}  
                
                if ($exclude.count -gt 0) {
                    
                    $omit = $exclude | ForEach-Object{$no = $_;$luns | Where-Object{$_.path -like $no}}
                    $luns = $luns | Where-Object{$_.Path -notin $omit.Path}
                
                }
                
                $numLuns = $luns.count
                $offline = $luns | Where-Object{$_.Online -ne $true}
                $online  = $luns | Where-Object{$_.Online -eq $true}
                $numOff  = $offline.count
                $numOn   = $online.count
                
                $netAppProp     = @{Name=$controller}
                $netAppProp     += @{TotalLuns=$numLuns}
                $netAppProp     += @{OfflineLuns=$numOff}
                $netAppProp     += @{OnlineLuns=$numOn} 
                $netAppProp     += @{Exclusions=$exclude}
                              
                if ($numOff -gt 0) {
                    
                    foreach ($off in $offline) {[array]$text += 'Lun: ' + $off.path}           
                    $netAppProp += @{NameOff=$text}
                    
                }
                
                $naO             = New-Object PSObject -Property $netAppProp
                $netAppObject   += $naO    

            }      
        
        }
   
        Display-PRTGText $netAppObject
    
    }
}

I wrote this to fill a need rather quickly. For error handling, the PRTG sensor itself does a good job at telling you why and where the script broke. As for variable renaming and cleanup, that is something I am always striving to do.

I hope you found this helpful, and as always feedback is appreciated!

-Ginger Ninja

     

PowerShell: Easy function for connecting to sessions on remote domain controllers

PowerShell: Easy function for connecting to sessions on remote domain controllers

One of the things I love doing with PowerShell is enabling my team to learn how to use it while providing tools that make specific or repetitive commands easy to use. 

Enter-PSSession is easy and straightforward to use, however it can be made even easier and used in a way to let users select from a list of servers, pick one, and then establish that connection.

Requirements

The Code

function Enter-DCSession {
    [cmdletbinding()]
    param(
        )
    $i = 0
    Write-Host "Enter PowerShell session on: ('q' exits to terminal)"`n -foregroundcolor $foregroundcolor
    foreach ($d in $ourDCs){
        if ($i -lt 10) {Write-Host "" $i -> $d.Name} else {Write-Host $i -> $d.Name}
        $i++
    
    }
   
    $enterSession = Read-Host "DC #?"
    While (!$adminCredential) { $adminCredential = Get-Credential -UserName 'domain\' -Message "Please input your admin account" }
if (($enterSession -ne "q") -and ($ourDCs.Name -contains $ourDCs[$enterSession].Name)) { 
        
        Write-Host "Connecting to remote session... type 'exit' to drop back to the local session."`n
        Enter-PSSession -ComputerName $ourDCs[$enterSession].Name -Credential $adminCredential
        
    } else {
        
        Write-Host `n'q or invalid option specified.' -ForegroundColor Red
        
    }
}

You can use this function in a module, and then call it via Enter-DCSession. It will then display a list of DCs stored in $ourDCs, and allow you to connect to one of them.

The logic in the script will only accept valid name selections as it compares the returned result to the names in the array $ourDCs.

if (($enterSession -ne "q") -and ($ourDCs.Name -contains $ourDCs[$enterSession].Name))

Come to think of it, I could actually just have the latter half of that if statement an it would work the same way. There's some cleanup I'll have to do :)

This script's logic can apply to a variety of things. You have a list of objects you want to take an action on in an array as a start, and then you use a foreach to do it. Using $i = 0 before the foreach, and then $i++ within the foreach, allows you to provide users a number to interact with that matches the number in the array. 

That is how this script takes numbers as an input that matches the name in the array.

I hope you found this helpful, and I'm always open to feedback!

-Ginger Ninja

 

PowerShell Quick Tip: Simple logging with time stamps

PowerShell Quick Tip: Simple logging with time stamps

Today I was writing a script at work to check sysvol replication. We have a DC that will sometimes not share the love outbound, yet inbound works just fine. That means to test it I needed to create a file on that DC, and ensure it replicates outward. Normally, I would check event log or use WMI queries, however everything looks good even when it isn't working.

After writing the script, I wanted to add some very simple logging so I could save the results a file (which is then emailed out). 

KISS 

I kept this simple, as I needed something that I could write while not taking up too much time to do it. In the script, I created a function to format a time stamp.

Timestamp function

function Get-TimeStamp {
    
    return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)
    
}

Told you it was simple! You can use it with Write-Output and use Out-File to add content into a log file.

Write-Output "$(Get-TimeStamp) I can connect to $dc" | Out-file C:\dclog.txt -append

Here are the results in the text file from the script after it runs:

[2/15/2016 9:27 AM] File test927AM created dc1
[2/15/2016 9:27 AM] File test927AM exists on dc1
[2/15/2016 9:30 AM] File test927AM removed from dc1
[2/15/2016 9:32 AM] I can connect to dc1

I added some Start-Sleep statements in there as you can see with the time stamp differences. Once I tidy up the sysvol replication checker script I'll share that here.

I hope you found this helpful, let me know if you have a better way or any questions/comments!

-Ginger Ninja

PowerShell: Fun with Weather Underground's API (part 2)

PowerShell: Fun with Weather Underground's API (part 2)

In the last post we learned how to use Weather Underground's API to resolve a city name and get data back from it. Once we get those two URLs we can use them to get data back from the API.  I also tied this into my LIFX light, which I posted about here.

Requirements

Setup

[cmdletbinding()]
param(
    [string]
    $city = (Read-Host "City?"),
    [string]
    $forecast = 'forecast',
    [boolean]
    $sendEmail,
    [boolean]
    $lifx,
    [boolean]
    $sendAlertEmail
)

if (!$foregroundColor) {$foregroundColor = 'green'}
$baseURL      = 'http://api.wunderground.com/api/'
$apiKey       = 'yourAPIKey'
$acceptHeader = 'application/json'

#"password" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString | Out-File .\emlpassword.txt

$emailPass    = Get-Content .\emlpassword.txt | ConvertTo-SecureString  
$weatherList  = Get-Content .\emails.txt
$alertList    = Get-Content .\alertEmails.txt
$emailUser    = 'yourEmailUsername'
$emailFrom    = 'youremail@service.com'
$smtpServer   = 'smtp.gmail.com'
$smtpPort     = '587'

The Setup

  • Name: Get-Weather.ps1
  • Variables:
    • $baseURL = (Weather Underground's base URL for the API, should be fine with default).
    • $apiKey = Your API key that you get from signing up.
    • $acceptHeader = Header/formatting settings (default is good).
    • $emailPass = Set your password by running the commented line above it. This will be user/machine encrypted.
    • $weatherList = Create a text file (named emails.txt) in the script directory that contains a list of people to email the forecasts to. 
    • $alertList  = Create a text file (named alertEmails.txt) in the script directory that contains a list of people to email/text alerts to. I made this different so I could setup text message alerts.
    • $emailUser = Your user name for the email address we're sending from.
    • $emailFrom  = Your from email address.
    • $smtpServer  = Your SMTP server. Default is Google's.
    • $smtpPort = Your SMTP server's port. This is Google's default.
  • Parameters
    • city (will prompt for one if left blank)
    • forecast (Will default to 4 day which is the value 'forecast')
      Values:
      • forecast  (4 day)
      • hourly (next 36 hours)
      • camera (emails an email address a random webcam from the city specified)
    • sendEmail (accepts $true, defaults to $false) This is for sending the email forecast.
    • lifx (accepts $true, defaults to $false) This is for LIFX light integration. Currently it will flash your LIFX light red when there is an alert. More coming soon with this!
    • sendAlertEmail (accepts $true, defaults to $false) This is to send an email (which I use the carrier's email to text address to text myself) if an alert is found.

The first function: Get-Weather

This function will first use the auto-complete API and then from there create the URLs and use them to get the weather data. It will also return the API results at the end. That way you can run the script like this:

$weatherChicago = .\Get-Weather.ps1 -city Chicago

And it will return what the API returns, as well as display some information:

The variable $weatherChicago will contain what the API returned. 

With this information you can really drill down and see what is available. It was by doing this that I got the initial ideas for parts of this script. Here's an example of the information returned via:

$weatherChicago.current_observation

After the function finds the city and displays the basic info, it then moves forward to check which forecast was specified and if that forecast is to be emailed. 

Get-Weather function code

function Get-Weather {
    param()
   
    $findMe = $city
    $find   = Invoke-RestMethod -Uri "http://autocomplete.wunderground.com/aq?query=$findMe"

    if ($find) {
        
        $cityAPI  = $find.Results[0].l
        $city     = $find.Results[0].name
        
        $fullURL  = $baseURL + $apiKey + "/features/conditions/hourly/forecast/webcams/alerts" + "$cityAPI.json"
        $radarURL = "http://api.wunderground.com/api/$apiKey/animatedradar/animatedsatellite" + "$cityAPI.gif?num=6&delay=50&interval=30"
        
        Write-Host `n"API URLS for $city" -foregroundcolor $foregroundColor
        Write-Host `t$fullURL
        Write-Host `t$radarURL
        
        $weatherForecast = Invoke-RestMethod -Uri $fullURL -ContentType $acceptHeader
        
        $currentCond     = $weatherForecast.current_observation
        
        Write-Host `n"Current Conditions for: $city" -foregroundColor $foregroundColor
        Write-Host $currentCond.Weather 
        Write-Host "Temperature:" $currentCond.temp_f"F"
        Write-Host "Winds:" $currentCond.wind_string
        
        $curAlerts = $weatherForecast.alerts 
        
        if ($curAlerts) {
            
            if ($lifx) { Get-LIFXAPI -action flashcolor -brightness 1 -color Red -state on }
            
            $typeName = Get-WeatherFunction -Weather 'alert' -value $weatherForecast.alerts
            
            $alertDate  = $curAlerts.date
            $alertExp   = $curAlerts.expires 
            $alertMsg   = $curAlerts.message
            
            Write-Host `n"Weather Alert! ($typeName)" -foregroundcolor Red
            Write-Host "Date: $alertDate Expires: $alertExp"
            Write-Host "$alertMsg"    
            
            if ($sendAlertEmail) {

                Foreach ($email in $alertList) {
                    
                    Send-WeatherEmail -to $email -Subject "Weather Alert!" -Body "Alert Type: $typeName City: $city Message: $alertMsg"
                
                }
            }                  

        } 
        
    }

    Switch ($forecast) {
    
        {$_ -eq 'hourly'} {
 
            if ($sendEmail) {

                $hourlyForecast = $weatherForecast.hourly_forecast
                
                $body = "<p></p>"
                $body += "<p>Here is your hourly forecast!</p>"
                
                $selCam   = Get-Random $weatherForecast.webcams.count
                
                $camImg   = $weatherforecast.webcams[$selCam].CURRENTIMAGEURL
                $camName  = $weatherForecast.webcams[$selCam].linktext
                $camLink  = $weatherForecast.webcams[$selCam].link
                
                $body += "<p>Random webcam shot from: <a href=`"$camLink`">$camName</a></p>"
                $body += "<p><img src=`"$camImg`"></p>"                 
                                
                $body += "<p>$city Radar:</p>"
                $body += "<p><img src=`"$radarURL`"></p>"  
                
                if ($curAlerts) {
                    
                    $body += "<p><b><font color=`"red`">Weather Alert! ($typeName)</font></b></p>"
                    $body += "<p>Date: $alertDate Expires: $alertExp</p>"
                    $body += "<p>$alertMsg</p>"    
    
                }           
                
                foreach ($hour in $hourlyForecast) {
                    
                    $body += "<p></p>"
                    $body += "<p></p>"
                    
                    $prettyTime       = $hour.fcttime.pretty
                    $hourTemp         = $hour.temp.english  
                    $hourImg          = $hour.icon_url
                    
                    [int]$hourChill   = $hour.windchill.english
                    
                    if ($hourChill -eq -9999) {
                    
                        $hourChilltxt = 'N/A'
                        
                    } else {
                        
                        $hourChilltxt = $hourChill.ToString() + 'F'
                   
                    }
                                        
                    $hourWind         = $hour.wspd.english
                    $windDir          = $hour.wdir.dir
                    $hourUV           = $hour.uvi
                    $dewPoint         = $hour.dewpoint.english
                    $hourFeels        = $hour.feelslike.english
                    $hourHum          = $hour.humidity
                    $conditions       = $hour.condition
                    [int]$hourPrecip  = $hour.pop
                    
                    $popText = Get-WeatherFunction -Weather 'preciptext' -value $hourPrecip
                    
                    $body += "<p><b>$prettyTime</b></p>"
                    $body += "<p><img src=`"$hourImg`">$conditions</p>"
                    $body += "<p>Chance of precipitation: $hourPrecip% / $popText</p>"
                    $body += "<p>Current Temp: $hourTemp`F Wind Chill: $hourChilltxt Feels Like: $hourFeels`F</p>"
                    $body += "<p>Dew Point: $dewPoint</p>"
                    $body += "<p>Wind Speed: $hourWind`mph Direction: $windDir</p>"
                    $body += "<p>Humidity: $hourHum%</p>"
                    $body += "<p>UV Index: $hourUV"     
                    
                }
                
                foreach ($email in $weatherList) {Send-WeatherEmail -To $email -Subject "Your hourly forecast for $city" -body $body}
            
            }            
        
        }
        
        {$_ -eq 'forecast'} {                
              
            if ($sendEmail) {

                $todayForecast = $weatherForecast.forecast.simpleforecast.forecastday
                
                $body = "<p></p>"
                $body += "<p>Here is your 4 day forecast!</p>"
                
                $selCam   = Get-Random $weatherForecast.webcams.count
                
                $camImg   = $weatherforecast.webcams[$selCam].CURRENTIMAGEURL
                $camName  = $weatherForecast.webcams[$selCam].linktext
                $camLink  = $weatherForecast.webcams[$selCam].link
                
                $body += "<p>Random webcam shot from: <a href=`"$camLink`">$camName</a></p>"
                $body += "<p><img src=`"$camImg`"></p>"                 
                
                $body += "<p>$city Radar:</p>"
                $body += "<p><img src=`"$radarURL`"></p>"      
                
                $curAlerts = $weatherForecast.alerts 
                
                if ($curAlerts) {
                    
                    $body += "<p><b><font color=`"red`">Weather Alert! ($typeName)</font></b></p>"
                    $body += "<p>Date: $alertDate Expires: $alertExp</p>"
                    $body += "<p>$alertMsg</p>"    

                }                   
               
                foreach ($day in $todayForecast) {
                    
                    $body += "<p></p>"
                    $body += "<p></p>"
                    
                    $dayImg          = $day.icon_url
                    $dayMonth        = $day.date.monthname
                    $dayDay          = $day.date.day
                    $dayName         = $day.date.weekday
                    $dayHigh         = $day.high.fahrenheit  
                    $dayLow          = $day.low.fahrenheit
                    $maxWind         = $day.maxwind.mph
                    $aveWind         = $day.avewind.mph
                    $aveHum          = $day.avehumidity
                    $conditions      = $day.conditions
                    [int]$dayPrecip  = $day.pop
                    
                    $popText = Get-WeatherFunction -Weather 'preciptext' -value $dayPrecip
                    
                    $body += "<p><b>$dayName, $dayMonth $dayDay</b></p>"
                    $body += "<p><img src=`"$dayImg`">$conditions</p>"
                    $body += "<p>Chance of precipitation: $dayPrecip% / $popText</p>"
                    $body += "<p>High: $dayHigh`F Low: $dayLow`F</p>"
                    $body += "<p>Ave Winds: $aveWind`mph Max Winds: $maxWind`mph</p>"
                    $body += "<p>Humidity: $aveHum%</p>"
         
                }

                foreach ($email in $weatherList) {Send-WeatherEmail -To $email -Subject "Your 4 day forecast for $city" -body $body}
            
            }  
            
        }

        {$_ -eq 'camera'} {
            
            $selCam    = Get-Random $weatherForecast.webcams.count
            
            $camImg    = $weatherforecast.webcams[$selCam].CURRENTIMAGEURL
            $camName   = $weatherForecast.webcams[$selCam].linktext
            $camLink   = $weatherForecast.webcams[$selCam].link
            
            $fileExt   = $camImg.SubString($camImg.LastIndexOf("."),4)
            
            $cityShort = $city.substring(0,$city.lastindexof(","))
            
            $fileName  = $cityShort + $fileExt
            
            $location  = (Get-Location).Path
            
            $camFile   = Invoke-WebRequest -Uri $camImg -OutFile "$location\$fileName"   
            
            $file = $location + "\" + $fileName
            
            $gallery = 'YourSquareSpaceGalleryOrEmailToSendAttachmentTo' 
            
            $SMTPClient             = New-Object Net.Mail.SmtpClient($SmtpServer, 587)
            $SMTPMessage            = New-Object System.Net.Mail.MailMessage($emailFrom,$gallery,"$city cam","$city cam")
            $SMTPClient.Credentials = New-Object System.Net.NetworkCredential($emailUser,$emailPass)
            
            $att                    = New-Object Net.Mail.Attachment($file)
            $SMTPClient.EnableSsl   = $true
           
            Switch (($fileName.substring($fileName.LastIndexOf(".")+1)).tolower()) {
                
                {$_ -like "*png*"} {
                
                    $typeExt = 'png'    
                    
                }
                
                {$_ -like "*jpg*"} {
                
                    $typeExt = 'jpg'     
                    
                }
                
                {$_ -like "*gif*"} {
                    
                    $typeExt = 'gif' 
                    
                }                                
                
            }
            
            $SMTPMessage.Attachments.Add($att)
            ($smtpmessage.Attachments).contenttype.mediatype = "image/$typeExt"
            
            $SMTPClient.Send($SMTPMessage)
            
            $att.Dispose()
            $SMTPMessage.Dispose()
            
            Remove-Item $file
            
        }
    
    } 
    
    Return $weatherForecast

}

Let's take a look at some of the code

The 'camera' option uses its own email sending commands since I had to change the MIME type of the attachment. There is also a variable used to specify where to send the email to. This variable is within the switch for now (until I clean it up!) as $gallery. You'll want to change that to an email address you want to receive the attached webcam shot.

 For some reason when sending an image file PowerShell sends it with type information stating it is an application. The following code is what I used to fix that. I noticed this issue when it wouldn't post to SquareSpace unless I first sent it to my Gmail, and then forwarded it to SquareSpace. Once I forwarded it I noticed it would post. It was then I decided to take a look at the full email details and saw the difference in attachment types.

That led me to write the following section of code so I could send the random webcam shot to the gallery and have it post.

{$_ -eq 'camera'} {
            
            $selCam    = Get-Random $weatherForecast.webcams.count
            
            $camImg    = $weatherforecast.webcams[$selCam].CURRENTIMAGEURL
            $camName   = $weatherForecast.webcams[$selCam].linktext
            $camLink   = $weatherForecast.webcams[$selCam].link
            
            $fileExt   = $camImg.SubString($camImg.LastIndexOf("."),4)
            
            $cityShort = $city.substring(0,$city.lastindexof(","))
            
            $fileName  = $cityShort + $fileExt
            
            $location  = (Get-Location).Path
            
            $camFile   = Invoke-WebRequest -Uri $camImg -OutFile "$location\$fileName"   
            
            $file = $location + "\" + $fileName
            
            $gallery = 'your SquareSpace gallery URL or Email address to send pictures to' 
            
            $SMTPClient             = New-Object Net.Mail.SmtpClient($SmtpServer, 587)
            $SMTPMessage            = New-Object System.Net.Mail.MailMessage($emailFrom,$gallery,"$city cam","$city cam")
            $SMTPClient.Credentials = New-Object System.Net.NetworkCredential($emailUser,$emailPass)
            
            $att                    = New-Object Net.Mail.Attachment($file)
            $SMTPClient.EnableSsl   = $true
           
            Switch (($fileName.substring($fileName.LastIndexOf(".")+1)).tolower()) {
                
                {$_ -like "*png*"} {
                
                    $typeExt = 'png'    
                    
                }
                
                {$_ -like "*jpg*"} {
                
                    $typeExt = 'jpg'     
                    
                }
                
                {$_ -like "*gif*"} {
                    
                    $typeExt = 'gif' 
                    
                }                                
                
            }
            
            $SMTPMessage.Attachments.Add($att)
            ($smtpmessage.Attachments).contenttype.mediatype = "image/$typeExt"
            
            $SMTPClient.Send($SMTPMessage)
            
            $att.Dispose()
            $SMTPMessage.Dispose()
            
            Remove-Item $file
            
        }

The above code downloads the file, looks at what type of image it is, and then changes the type accordingly. After that it constructs an email object, attaches the file, send the email, and finally removes the file.

The next function is Send-WeatherEmail

function Send-WeatherEmail {
    [cmdletbinding()]
    param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [string]
    $To,
    
    [string]
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    $Subject,
    
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    $Body,
    
    [string]
    $attachment
    )

    if (!$to)      {Write-Error "No recipient specified";break}
    if (!$subject) {Write-Error "No subject specified";break}
    if (!$body)    {Write-Error "No body specified";break}
   
    $gmailCredential = New-Object System.Management.Automation.PSCredential($emailUser,$emailPass)
       
    Send-MailMessage -To $to -From $emailFrom -Body $body -BodyAsHtml:$true -Subject $Subject -SmtpServer $smtpServer -Port $smtpPort -UseSsl -Credential $gmailCredential
 
}

This function is a short and sweet function that wraps up Send-MailMessage so you can use it throughout the script easily.

Now onto the function Get-WeatherFunction

function Get-WeatherFunction {
    [cmdletbinding()]
    param(
        [string]
        $weather,
        $value       
    )
    
    Switch ($weather) {
        
        {$_ -eq 'preciptext'} {
        
            Switch ($value) {
                
                {$_ -lt 20} {
                    
                    $popText = 'No mention'
                    
                }
                
                {$_ -eq 20} {
                    
                    $popText = 'Slight Chance'
                    
                }
                
                {($_ -lt 50 -and $_ -gt 20)} {
                    
                    $popText = 'Chance'
                    
                }
                
                {$_ -eq 50} {
                    
                    $popText = 'Good chance'
                    
                }
                
                {($_ -lt 70 -and $_ -gt 50)} {
                    
                    $popText = 'Likely'
                    
                }
                
                {$_ -ge 70} {
                    
                    $popText = 'Extremely likely'
                    
                }
                
            }
            
            Return $popText
            
        }   
        
        {$_ -eq 'alert'}    {
            
            Switch ($curAlerts.type) {
                
                'HEA' {$typeName = 'Heat Advisory'}
                'TOR' {$typeName = 'Tornado Warning'}
                'TOW' {$typeName = 'Tornado Watch'}
                'WRN' {$typeName = 'Severe Thunderstorm Warning'}
                'SEW' {$typeName = 'Severe Thunderstorm Watch'}
                'WIN' {$typeName = 'Winter Weather Advisory'}
                'FLO' {$typeName = 'Flood Warning'}
                'WAT' {$typeName = 'Flood Watch / Statement'}
                'WND' {$typeName = 'High Wind Advisory'}
                'SVR' {$typeName = 'Severe Weather Statement'}
                'HEA' {$typeName = 'Heat Advisory'}
                'FOG' {$typeName = 'Dense Fog Advisory'}
                'SPE' {$typeName = 'Special Weather Statement'}
                'FIR' {$typeName = 'Fire Weather Advisory'}
                'VOL' {$typeName = 'Volcanic Activity Statement'}
                'HWW' {$typeName = 'High Wind Warning'}
                'REC' {$typeName = 'Record Set'}
                'REP' {$typeName = 'Public Reports'}
                'PUB' {$typeName = 'Public Information Statement'}
                    
            }
                
            Return $typeName
    
        }    
    }
    
}

This is perhaps one of the gnarliest functions I've ever named. I'll have to rethink the name, perhaps. For now, let's look at what it does. 

Currently it will take the parameter $weather and take different actions based on the input. If you specify 'preciptext' it will return the precipitation chance in text format based on the number it is given. For example if you run: 

Get-WeatherFunction -Weather 'preciptext' -value 71

It will return 'Extremely Likely'.

If you specify 'alert' then you'll be able to provide the 3 letter alert code Weather Underground returns via the API and return the full alert text. For example: 

Get-WeatherFunction -Weather 'alert' -value 'HEA'

This would return 'Heat Advisory'.

The final function: Get-LIFXApi

function Get-LIFXApi {
[cmdletbinding()]
param(
    [string]
    $action = 'setstate',
    [string]
    $state = 'on',
    [string]
    $color = 'white',
    [double]
    $brightness = '0.4'
)

$apiKey          = 'YourLifxApiKey'
$base64Key       =  [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(($apiKey)))
$headers         =  @{Authorization=("Basic {0}" -f $base64Key)}
$allURL          = 'https://api.lifx.com/v1/lights/all' 
$acceptheader    = 'application/json'
$baseURL         = 'https://api.lifx.com/v1/lights/'
$foregroundColor = 'white'
[array]$colors   = @('white','red','orange','yellow','cyan','green','blue','purple','pink')

$ninjaLights     = Invoke-RestMethod -headers $headers -Uri $allURL

function Change-LightState {
    [cmdletbinding()]
    param(
        [string]
        $state,
        [string]
        $color,
        [double]
        $brightness,
        [string]
        $selector
    )
    
    $ninjaID  = $ninjaLights.id
    $selector = 'id:' + $ninjaID
    $fullURL  = $baseurl + $selector + '/state'

    $payloadBuilder = "{
                    `"power`": `"$state`",
                    `"color`": `"$color`",
                    `"brightness`" : $brightness 
                    }"

    Write-Host "Changing light to:" -ForegroundColor $foregroundcolor
    Write-Host `t"State     :" $state
    Write-Host `t"Color     :" $color
    Write-Host `t"Brightness:" ($brightness * 100)"%" `n

    $stateReturn = Invoke-RestMethod -headers $headers -uri $fullURL -Method Put -Body $payloadBuilder
    
    Write-Host "API status:" -ForegroundColor $foregroundcolor
    Write-Host `t"Light :" $stateReturn.results.label
    Write-Host `t"Status:" $stateReturn.results.status `n
    
    
}

Switch ($action) {
    
    'setstate' { Change-LightState -state $state -color $color -brightness $brightness -selector $selector }
    
    'fluxtest' { 
        
        $originalBrightness = $ninjaLights.Brightness
        $originalColor      = $ninjalights.Color
        $originalState      = $ninjalights.Power
        $colorString        = "hue:" + $originalcolor.hue + " saturation:" + $originalcolor.saturation + " kelvin:" + $originalColor.Kelvin       
          
        $i = 0
        
        While ($i -le 20) {
            
            $color              = Get-Random $colors
            [double]$brightness = "{0:n2}" -f (Get-Random 0.99)
 
            Change-LightState -color $color -brightness $brightness -selector $selector -state $state
            Start-Sleep -seconds 1
            $i++
            
        }
        
        Change-LightState -state $originalState -color $colorString -brightness $originalBrightness -selector $selector  
        
    } 
    
    'flashcolor' {
        
        $originalBrightness = $ninjaLights.Brightness
        $originalColor      = $ninjalights.Color
        $originalState      = $ninjalights.Power
        $colorString        = "hue:" + $originalcolor.hue + " saturation:" + $originalcolor.saturation + " kelvin:" + $originalColor.Kelvin
        
        Change-LightState -state $state -color $color -brightness $brightness -selector $selector
        Start-Sleep -Seconds 1
        
        Change-LightState -state $originalState -color $colorString -brightness $originalBrightness -selector $selector
        Start-Sleep -Seconds 1
                
        Change-LightState -state $state -color $color -brightness $brightness -selector $selector
        Start-Sleep -Seconds 1
        
        Change-LightState -state $originalState -color $colorString -brightness $originalBrightness -selector $selector
        Start-Sleep -Seconds 1        
        
        Change-LightState -state $state -color $color -brightness $brightness -selector $selector
        Start-Sleep -Seconds 1
        
        Change-LightState -state $originalState -color $colorString -brightness $originalBrightness -selector $selector
                        
    }
}    
    
    
}

I wrote the above function as a script initially when I learned that LIFX provides and API you can use to control your light! It is currently called if the parameter lifx is $true, and if there is a current alert.

if ($lifx) { Get-LIFXAPI -action flashcolor -brightness 1 -color Red -state on }

It then performs the switch option 'flashcolor' which flashes the light red at 100%. It then returns the light to whatever setting it was set to before it started flashing. To get this function to work you'll need to change $apiKey to your LIFX Cloud API key. For more details on working with the LIFX API, check out my post here.

Full code

[cmdletbinding()]
param(
    [string]
    $city = (Read-Host "City?"),
    [string]
    $forecast = 'forecast',
    [boolean]
    $sendEmail,
    [boolean]
    $lifx,
    [boolean]
    $sendAlertEmail
)

if (!$foregroundColor) {$foregroundColor = 'green'}
$baseURL      = 'http://api.wunderground.com/api/'
$apiKey       = 'yourAPIKey'
$acceptHeader = 'application/json'

#"password" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString | Out-File .\emlpassword.txt

$emailPass    = Get-Content .\emlpassword.txt | ConvertTo-SecureString  
$weatherList  = Get-Content .\emails.txt
$alertList    = Get-Content .\alertEmails.txt
$emailUser    = 'yourEmailUsername'
$emailFrom    = 'youremail@service.com'
$smtpServer   = 'smtp.gmail.com'
$smtpPort     = '587'

function Get-Weather {
    param()
   
    $findMe = $city
    $find   = Invoke-RestMethod -Uri "http://autocomplete.wunderground.com/aq?query=$findMe"

    if ($find) {
        
        $cityAPI  = $find.Results[0].l
        $city     = $find.Results[0].name
        
        $fullURL  = $baseURL + $apiKey + "/features/conditions/hourly/forecast/webcams/alerts" + "$cityAPI.json"
        $radarURL = "http://api.wunderground.com/api/$apiKey/animatedradar/animatedsatellite" + "$cityAPI.gif?num=6&delay=50&interval=30"
        
        Write-Host `n"API URLS for $city" -foregroundcolor $foregroundColor
        Write-Host `t$fullURL
        Write-Host `t$radarURL
        
        $weatherForecast = Invoke-RestMethod -Uri $fullURL -ContentType $acceptHeader
        
        $currentCond     = $weatherForecast.current_observation
        
        Write-Host `n"Current Conditions for: $city" -foregroundColor $foregroundColor
        Write-Host $currentCond.Weather 
        Write-Host "Temperature:" $currentCond.temp_f"F"
        Write-Host "Winds:" $currentCond.wind_string
        
        $curAlerts = $weatherForecast.alerts 
        
        if ($curAlerts) {
            
            if ($lifx) { Get-LIFXAPI -action flashcolor -brightness 1 -color Red -state on }
            
            $typeName = Get-WeatherFunction -Weather 'alert' -value $weatherForecast.alerts
            
            $alertDate  = $curAlerts.date
            $alertExp   = $curAlerts.expires 
            $alertMsg   = $curAlerts.message
            
            Write-Host `n"Weather Alert! ($typeName)" -foregroundcolor Red
            Write-Host "Date: $alertDate Expires: $alertExp"
            Write-Host "$alertMsg"    
            
            if ($sendAlertEmail) {

                Foreach ($email in $alertList) {
                    
                    Send-WeatherEmail -to $email -Subject "Weather Alert!" -Body "Alert Type: $typeName City: $city Message: $alertMsg"
                
                }
            }                  

        } 
        
    }

    Switch ($forecast) {
    
        {$_ -eq 'hourly'} {
 
            if ($sendEmail) {

                $hourlyForecast = $weatherForecast.hourly_forecast
                
                $body = "<p></p>"
                $body += "<p>Here is your hourly forecast!</p>"
                
                $selCam   = Get-Random $weatherForecast.webcams.count
                
                $camImg   = $weatherforecast.webcams[$selCam].CURRENTIMAGEURL
                $camName  = $weatherForecast.webcams[$selCam].linktext
                $camLink  = $weatherForecast.webcams[$selCam].link
                
                $body += "<p>Random webcam shot from: <a href=`"$camLink`">$camName</a></p>"
                $body += "<p><img src=`"$camImg`"></p>"                 
                                
                $body += "<p>$city Radar:</p>"
                $body += "<p><img src=`"$radarURL`"></p>"  
                
                if ($curAlerts) {
                    
                    $body += "<p><b><font color=`"red`">Weather Alert! ($typeName)</font></b></p>"
                    $body += "<p>Date: $alertDate Expires: $alertExp</p>"
                    $body += "<p>$alertMsg</p>"    
    
                }           
                
                foreach ($hour in $hourlyForecast) {
                    
                    $body += "<p></p>"
                    $body += "<p></p>"
                    
                    $prettyTime       = $hour.fcttime.pretty
                    $hourTemp         = $hour.temp.english  
                    $hourImg          = $hour.icon_url
                    
                    [int]$hourChill   = $hour.windchill.english
                    
                    if ($hourChill -eq -9999) {
                    
                        $hourChilltxt = 'N/A'
                        
                    } else {
                        
                        $hourChilltxt = $hourChill.ToString() + 'F'
                   
                    }
                                        
                    $hourWind         = $hour.wspd.english
                    $windDir          = $hour.wdir.dir
                    $hourUV           = $hour.uvi
                    $dewPoint         = $hour.dewpoint.english
                    $hourFeels        = $hour.feelslike.english
                    $hourHum          = $hour.humidity
                    $conditions       = $hour.condition
                    [int]$hourPrecip  = $hour.pop
                    
                    $popText = Get-WeatherFunction -Weather 'preciptext' -value $hourPrecip
                    
                    $body += "<p><b>$prettyTime</b></p>"
                    $body += "<p><img src=`"$hourImg`">$conditions</p>"
                    $body += "<p>Chance of precipitation: $hourPrecip% / $popText</p>"
                    $body += "<p>Current Temp: $hourTemp`F Wind Chill: $hourChilltxt Feels Like: $hourFeels`F</p>"
                    $body += "<p>Dew Point: $dewPoint</p>"
                    $body += "<p>Wind Speed: $hourWind`mph Direction: $windDir</p>"
                    $body += "<p>Humidity: $hourHum%</p>"
                    $body += "<p>UV Index: $hourUV"     
                    
                }
                
                foreach ($email in $weatherList) {Send-WeatherEmail -To $email -Subject "Your hourly forecast for $city" -body $body}
            
            }            
        
        }
        
        {$_ -eq 'forecast'} {                
              
            if ($sendEmail) {

                $todayForecast = $weatherForecast.forecast.simpleforecast.forecastday
                
                $body = "<p></p>"
                $body += "<p>Here is your 4 day forecast!</p>"
                
                $selCam   = Get-Random $weatherForecast.webcams.count
                
                $camImg   = $weatherforecast.webcams[$selCam].CURRENTIMAGEURL
                $camName  = $weatherForecast.webcams[$selCam].linktext
                $camLink  = $weatherForecast.webcams[$selCam].link
                
                $body += "<p>Random webcam shot from: <a href=`"$camLink`">$camName</a></p>"
                $body += "<p><img src=`"$camImg`"></p>"                 
                
                $body += "<p>$city Radar:</p>"
                $body += "<p><img src=`"$radarURL`"></p>"      
                
                $curAlerts = $weatherForecast.alerts 
                
                if ($curAlerts) {
                    
                    $body += "<p><b><font color=`"red`">Weather Alert! ($typeName)</font></b></p>"
                    $body += "<p>Date: $alertDate Expires: $alertExp</p>"
                    $body += "<p>$alertMsg</p>"    

                }                   
               
                foreach ($day in $todayForecast) {
                    
                    $body += "<p></p>"
                    $body += "<p></p>"
                    
                    $dayImg          = $day.icon_url
                    $dayMonth        = $day.date.monthname
                    $dayDay          = $day.date.day
                    $dayName         = $day.date.weekday
                    $dayHigh         = $day.high.fahrenheit  
                    $dayLow          = $day.low.fahrenheit
                    $maxWind         = $day.maxwind.mph
                    $aveWind         = $day.avewind.mph
                    $aveHum          = $day.avehumidity
                    $conditions      = $day.conditions
                    [int]$dayPrecip  = $day.pop
                    
                    $popText = Get-WeatherFunction -Weather 'preciptext' -value $dayPrecip
                    
                    $body += "<p><b>$dayName, $dayMonth $dayDay</b></p>"
                    $body += "<p><img src=`"$dayImg`">$conditions</p>"
                    $body += "<p>Chance of precipitation: $dayPrecip% / $popText</p>"
                    $body += "<p>High: $dayHigh`F Low: $dayLow`F</p>"
                    $body += "<p>Ave Winds: $aveWind`mph Max Winds: $maxWind`mph</p>"
                    $body += "<p>Humidity: $aveHum%</p>"
         
                }

                foreach ($email in $weatherList) {Send-WeatherEmail -To $email -Subject "Your 4 day forecast for $city" -body $body}
            
            }  
            
        }

        {$_ -eq 'camera'} {
            
            $selCam    = Get-Random $weatherForecast.webcams.count
            
            $camImg    = $weatherforecast.webcams[$selCam].CURRENTIMAGEURL
            $camName   = $weatherForecast.webcams[$selCam].linktext
            $camLink   = $weatherForecast.webcams[$selCam].link
            
            $fileExt   = $camImg.SubString($camImg.LastIndexOf("."),4)
            
            $cityShort = $city.substring(0,$city.lastindexof(","))
            
            $fileName  = $cityShort + $fileExt
            
            $location  = (Get-Location).Path
            
            $camFile   = Invoke-WebRequest -Uri $camImg -OutFile "$location\$fileName"   
            
            $file = $location + "\" + $fileName
            
            $gallery = 'YourSquareSpaceGalleryOrEmailToSendAttachmentTo' 
            
            $SMTPClient             = New-Object Net.Mail.SmtpClient($SmtpServer, 587)
            $SMTPMessage            = New-Object System.Net.Mail.MailMessage($emailFrom,$gallery,"$city cam","$city cam")
            $SMTPClient.Credentials = New-Object System.Net.NetworkCredential($emailUser,$emailPass)
            
            $att                    = New-Object Net.Mail.Attachment($file)
            $SMTPClient.EnableSsl   = $true
           
            Switch (($fileName.substring($fileName.LastIndexOf(".")+1)).tolower()) {
                
                {$_ -like "*png*"} {
                
                    $typeExt = 'png'    
                    
                }
                
                {$_ -like "*jpg*"} {
                
                    $typeExt = 'jpg'     
                    
                }
                
                {$_ -like "*gif*"} {
                    
                    $typeExt = 'gif' 
                    
                }                                
                
            }
            
            $SMTPMessage.Attachments.Add($att)
            ($smtpmessage.Attachments).contenttype.mediatype = "image/$typeExt"
            
            $SMTPClient.Send($SMTPMessage)
            
            $att.Dispose()
            $SMTPMessage.Dispose()
            
            Remove-Item $file
            
        }
    
    } 
    
    Return $weatherForecast

}

function Send-WeatherEmail {
    [cmdletbinding()]
    param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [string]
    $To,
    
    [string]
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    $Subject,
    
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    $Body,
    
    [string]
    $attachment
    )

    if (!$to)      {Write-Error "No recipient specified";break}
    if (!$subject) {Write-Error "No subject specified";break}
    if (!$body)    {Write-Error "No body specified";break}
   
    $gmailCredential = New-Object System.Management.Automation.PSCredential($emailUser,$emailPass)
       
    Send-MailMessage -To $to -From $emailFrom -Body $body -BodyAsHtml:$true -Subject $Subject -SmtpServer $smtpServer -Port $smtpPort -UseSsl -Credential $gmailCredential
 
}

function Get-WeatherFunction {
    [cmdletbinding()]
    param(
        [string]
        $weather,
        $value       
    )
    
    Switch ($weather) {
        
        {$_ -eq 'preciptext'} {
        
            Switch ($value) {
                
                {$_ -lt 20} {
                    
                    $popText = 'No mention'
                    
                }
                
                {$_ -eq 20} {
                    
                    $popText = 'Slight Chance'
                    
                }
                
                {($_ -lt 50 -and $_ -gt 20)} {
                    
                    $popText = 'Chance'
                    
                }
                
                {$_ -eq 50} {
                    
                    $popText = 'Good chance'
                    
                }
                
                {($_ -lt 70 -and $_ -gt 50)} {
                    
                    $popText = 'Likely'
                    
                }
                
                {$_ -ge 70} {
                    
                    $popText = 'Extremely likely'
                    
                }
                
            }
            
            Return $popText
            
        }   
        
        {$_ -eq 'alert'}    {
            
            Switch ($curAlerts.type) {
                
                'HEA' {$typeName = 'Heat Advisory'}
                'TOR' {$typeName = 'Tornado Warning'}
                'TOW' {$typeName = 'Tornado Watch'}
                'WRN' {$typeName = 'Severe Thunderstorm Warning'}
                'SEW' {$typeName = 'Severe Thunderstorm Watch'}
                'WIN' {$typeName = 'Winter Weather Advisory'}
                'FLO' {$typeName = 'Flood Warning'}
                'WAT' {$typeName = 'Flood Watch / Statement'}
                'WND' {$typeName = 'High Wind Advisory'}
                'SVR' {$typeName = 'Severe Weather Statement'}
                'HEA' {$typeName = 'Heat Advisory'}
                'FOG' {$typeName = 'Dense Fog Advisory'}
                'SPE' {$typeName = 'Special Weather Statement'}
                'FIR' {$typeName = 'Fire Weather Advisory'}
                'VOL' {$typeName = 'Volcanic Activity Statement'}
                'HWW' {$typeName = 'High Wind Warning'}
                'REC' {$typeName = 'Record Set'}
                'REP' {$typeName = 'Public Reports'}
                'PUB' {$typeName = 'Public Information Statement'}
                    
            }
                
            Return $typeName
    
        }    
    }
    
}

function Get-LIFXApi {
[cmdletbinding()]
param(
    [string]
    $action = 'setstate',
    [string]
    $state = 'on',
    [string]
    $color = 'white',
    [double]
    $brightness = '0.4'
)

$apiKey          = 'YourLifxApiKey'
$base64Key       =  [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(($apiKey)))
$headers         =  @{Authorization=("Basic {0}" -f $base64Key)}
$allURL          = 'https://api.lifx.com/v1/lights/all' 
$acceptheader    = 'application/json'
$baseURL         = 'https://api.lifx.com/v1/lights/'
$foregroundColor = 'white'
[array]$colors   = @('white','red','orange','yellow','cyan','green','blue','purple','pink')

$ninjaLights     = Invoke-RestMethod -headers $headers -Uri $allURL

function Change-LightState {
    [cmdletbinding()]
    param(
        [string]
        $state,
        [string]
        $color,
        [double]
        $brightness,
        [string]
        $selector
    )
    
    $ninjaID  = $ninjaLights.id
    $selector = 'id:' + $ninjaID
    $fullURL  = $baseurl + $selector + '/state'

    $payloadBuilder = "{
                    `"power`": `"$state`",
                    `"color`": `"$color`",
                    `"brightness`" : $brightness 
                    }"

    Write-Host "Changing light to:" -ForegroundColor $foregroundcolor
    Write-Host `t"State     :" $state
    Write-Host `t"Color     :" $color
    Write-Host `t"Brightness:" ($brightness * 100)"%" `n

    $stateReturn = Invoke-RestMethod -headers $headers -uri $fullURL -Method Put -Body $payloadBuilder
    
    Write-Host "API status:" -ForegroundColor $foregroundcolor
    Write-Host `t"Light :" $stateReturn.results.label
    Write-Host `t"Status:" $stateReturn.results.status `n
    
    
}

Switch ($action) {
    
    'setstate' { Change-LightState -state $state -color $color -brightness $brightness -selector $selector }
    
    'fluxtest' { 
        
        $originalBrightness = $ninjaLights.Brightness
        $originalColor      = $ninjalights.Color
        $originalState      = $ninjalights.Power
        $colorString        = "hue:" + $originalcolor.hue + " saturation:" + $originalcolor.saturation + " kelvin:" + $originalColor.Kelvin       
          
        $i = 0
        
        While ($i -le 20) {
            
            $color              = Get-Random $colors
            [double]$brightness = "{0:n2}" -f (Get-Random 0.99)
 
            Change-LightState -color $color -brightness $brightness -selector $selector -state $state
            Start-Sleep -seconds 1
            $i++
            
        }
        
        Change-LightState -state $originalState -color $colorString -brightness $originalBrightness -selector $selector  
        
    } 
    
    'flashcolor' {
        
        $originalBrightness = $ninjaLights.Brightness
        $originalColor      = $ninjalights.Color
        $originalState      = $ninjalights.Power
        $colorString        = "hue:" + $originalcolor.hue + " saturation:" + $originalcolor.saturation + " kelvin:" + $originalColor.Kelvin
        
        Change-LightState -state $state -color $color -brightness $brightness -selector $selector
        Start-Sleep -Seconds 1
        
        Change-LightState -state $originalState -color $colorString -brightness $originalBrightness -selector $selector
        Start-Sleep -Seconds 1
                
        Change-LightState -state $state -color $color -brightness $brightness -selector $selector
        Start-Sleep -Seconds 1
        
        Change-LightState -state $originalState -color $colorString -brightness $originalBrightness -selector $selector
        Start-Sleep -Seconds 1        
        
        Change-LightState -state $state -color $color -brightness $brightness -selector $selector
        Start-Sleep -Seconds 1
        
        Change-LightState -state $originalState -color $colorString -brightness $originalBrightness -selector $selector
                        
    }
}    
    
    
}

Get-Weather

Examples:

.\Get-Weather.ps1 -city houston -forecast hourly -sendEmail $true

And here is the email:

$weather = .\Get-Weather.ps1 -city Chicago -lifx $true

As you can see in the above example, the light will flash red. Then it begins  alternating with the state it was in beforehand, and finally it returns the light to the original state it was in.

More to come!

I've been writing this Get-Weather script for a little while now, and there's more I want to do with it. For now that list is:

  • Clean up the code a bit.
  • Add help nodes.
  • Add more error checking.
  • Find out more ways to utilize both the Weather Underground API and the LIFX API.

In the next post I will detail how I use this script in a scheduled task to send out daily weather emails.

As always, let me know if you have any questions, feedback, or ideas!

-Ginger Ninja

PowerShell: Control your LIFX light!

PowerShell: Control your LIFX light!

LET THERE BE LIGHT!

This post has been updated on 5/18/17 to reflect changes made to the Lifx API!

I stumbled upon the fact that LIFX has an API you can use today. Having found that out I wanted to do it in PowerShell and see what I can do. As it turns out there's a decent amount of things you can do and their API is "limited" to around 1 call per second.

If you would like to get into even more detail, step-by-step, and behind the scenes of how this works, check out this post!

Requirements

To get started with this you'll need...

Getting started

Here's what I did to get started. 

  1. Created a PowerShell script named Get-LifxApi.ps1.
  2. Opened the script up with basic parameters and variables to get started.
  3. Somewhat annoying my girlfriend with the light changing so much  ❤️

Starting code

[cmdletbinding(DefaultParameterSetName='SetState')]
param(
    [Parameter(
        ParameterSetName = "StateSetting"
    )]        
    [ValidateSet('SetState','FlashColor','RandomColors','ListLights')]    
    [string]
    $Action = 'SetState',
    [Parameter(
        ParameterSetName = "StateSetting"
    )]    
    [ValidateSet('on','off')]
    [string]
    $State = 'on',
    [Parameter(
        ParameterSetName = "StateSetting"
    )]    
    [ValidateSet('white','red','orange','yellow','cyan','green','blue','purple','pink')]
    [string]
    $Color = 'white',
    [Parameter(
        ParameterSetName = "StateSetting"
    )]    
    [ValidateRange(0.0,1)]
    [double]
    $Brightness = '0.4',
    [Parameter(
        ParameterSetName = "Toggle"    
    )]    
    [Switch]
    $Toggle
)

$apiKey          = 'YourKeyGoesHere'
$headers         =  @{Authorization=("Bearer {0}" -f $apiKey)}
$allURL          = 'https://api.lifx.com/v1/lights/all' 
$acceptHeader    = 'application/json'
$baseURL         = 'https://api.lifx.com/v1/lights'
$foregroundColor = 'white'
[array]$colors   = @('white','red','orange','yellow','cyan','green','blue','purple','pink')

Notes

You'll want to change the $apiKey to match your API key from the LIFX cloud.
The $allURL variable will return all your lights, and then the $baseURL variable is used later when formatting calls to change colors or do other things. $foregroundColor let's you specify a color for some of the Write-Host returns, and $colors is an array of colors currently accepted by the API (and you'll see why it is there later in this post). 

Let's make our first call!

I store the results of my light(s) in the $lifxLights variable.

$lifxLights = Invoke-RestMethod -headers $headers -Uri $allURL -ContentType $acceptheader

And now to write our first function in this script.

This function will allow us to utilize the API's state setting option. I named this function Set-LightState

function Set-LightState { #Begin function Set-LightState
    [cmdletbinding()]
    param(
        [Parameter()]
        [ValidateSet('on','off')]
        [string]
        $state,
        [Parameter()]  
        [string]
        $color,
        [Parameter()]
        [ValidateRange(0.0,1)]
        [double]
        $brightness,
        [string]
        $selector
    )

    if ($lifxLight.Connected) {          

        $fullURL  = "$baseurl/$($lifxLight.id)/state"        
        $payload  = [PSCustomObject]@{

            power      = $state
            color      = $color
            brightness = $brightness

        }

        $payloadJson = $payload | ConvertTo-Json

        Write-Host "Attempting to Change light to:" -ForegroundColor $foregroundcolor
        Write-Host `t"State     :" $state
        Write-Host `t"Color     :" $color
        Write-Host `t"Brightness:" ($brightness * 100)"%" `n

        $stateReturn = Invoke-RestMethod -headers $headers -uri $fullURL -Method Put -Body $payloadJson -ContentType $acceptheader
    
        Write-Host "API status:" -ForegroundColor $foregroundcolor
        Write-Host `t"Light :" $stateReturn.results.label
        Write-Host `t"Status:" $stateReturn.results.status `n
    
    } else {
        
        $lightName = $lifxLight.label
        Write-Host "$lightName is offline or unreachable :("
        
    }    

} #End function Set-LightState

I only have one LIFX light. In the start of this function I set my 'selector' (a required field for API calls to LIFX when setting states) as the light's ID.  I then use the value of light's ID to setup the URL structure for the call. $fullURL becomes the URL used when accessing the API to set the state of the light.

Let's take some action!

At the start of the script I have a parameter for action. 
Below is how it is used, in conjunction with toggle.

if ($toggle) { #If we're just toggling
             
    $fullURL  = "$baseurl/$($lifxLight.id)/toggle"  
    $toggleResult = Invoke-RestMethod -Uri $fullURL -Method Post -ContentType $acceptHeader -Headers $headers

    if ($toggleResult.results.status -eq 'ok') {

        Write-Host "Light [$($toggleResult.results.label)] was toggled!"

    } else {

        Write-Host "Something went wrong: [$($toggleResult.results.status)]"
    }

} else { #Else we perform the actions!

    Switch ($action) { #Begin switch for script actions
        
        'SetState' {         
            
            Set-LightState -state $state -color $color -brightness $brightness

        }    
        
        'ListLights' {
            
            return $lifxLight
            
        }       

        'RandomColors' { 
            
            $originalBrightness = $lifxLight.Brightness
            $originalColor      = $lifxLight.Color
            $originalState      = $lifxLight.Power
            $colorString        = "hue:" + $originalcolor.hue + " saturation:" + $originalcolor.saturation + " kelvin:" + $originalColor.Kelvin       
            
            $i = 0
            
            While ($i -le 10) {
                
                $color              = Get-Random $colors
                [double]$brightness = "{0:n2}" -f (Get-Random -Maximum 1 -Minimum 0.00)
    
                Set-LightState -color $color -brightness $brightness -selector $selector -state $state
                Start-Sleep -seconds 1
                $i++
                
            }
            
            Set-LightState -state $originalState -color $colorString -brightness $originalBrightness

        }
        
        'FlashColor' {
            
            $originalBrightness = $lifxLights.Brightness
            $originalColor      = $lifxLights.Color
            $originalState      = $lifxLights.Power
            $colorString        = "hue:" + $originalcolor.hue + " saturation:" + $originalcolor.saturation + " kelvin:" + $originalColor.Kelvin
            
            Set-LightState -state $state -color $color -brightness $brightness
            Start-Sleep -Seconds 1
            
            Set-LightState -state $originalState -color $colorString -brightness $originalBrightness
            Start-Sleep -Seconds 1
                    
            Set-LightState -state $state -color $color -brightness $brightness
            Start-Sleep -Seconds 1
            
            Set-LightState -state $originalState -color $colorString -brightness $originalBrightness 
            Start-Sleep -Seconds 1        
            
            Set-LightState -state $state -color $color -brightness $brightness
            Start-Sleep -Seconds 1
            
            Set-LightState -state $originalState -color $colorString -brightness $originalBrightness
                            
        }    
    
    } #End switch for light actions
}

My favorite action is the RandomColors. I have that set to use Get-Random to set the color and brightness randomly.  The other action is setstate. What that will do is set the light to a state you specify. If you do not give it options it will default to turning the light on to a brightness of 40% (0.4) and the color value of white (this is defined as per the default options for the parameters at the start of this script). You can change them by supplying the arguments when you run the script.

$fullURL example contents:

https://api.lifx.com/v1/lights/az0321123abc/state

Examples

.\Get-LifxAPI.ps1 -Action setstate -State on -Color blue -Brightness 0.5
lifx.PNG

Did it work?! 
(Yup)

.\Get-LifxAPI.ps1 -Action setstate

The above example is the simplest way to run it as it utilizes the default parameters. (actually, you could just run Get-LifxAPI.ps1)

Now for some fun!

.\Get-LifxAPI.ps1 -Action RandomColors

More to come!

I will be adding to this as I work on it more and clean it up. I will get adding:

  • Help nodes for Get-Help (done!).
  • More options to leverage different parts of the LIFX API (coming).
  • And more as I think of cool things to do with this!

Use:

Get-Help .\Get-LifxAPI.ps1 -Detailed

To see the help that is included. Be sure to change Get-LifxAPI.ps1 to what you named your script to!

The Code

<#
.SYNOPSIS   
    Use this script to control your Lifx light via PowerShell!
.DESCRIPTION 
    You can use this script to turn your light on and off, as well as change the brightness and colors.
.PARAMETER Action

    Argument: The action you'd like to take:
    SetState, FlashColor, RandomColors, and ListLights are valid actions.

.PARAMETER State
    This parameter accepts the state you'd like to set: on or off.
.PARAMETER Color
    Argument: The color you'd like to set the light to. 
    Valid colors can be tab completed.
.PARAMETER Brightness
    Argument: The brightness you want to set.
    Valid range: between 0-1 (0.5 being 50%)  
.PARAMETER Toggle
    Toggles the light      
.NOTES   
    Name: Get-LifxAPI.ps1
    Author: Mike Roberts aka Ginger Ninja
    DateCreated: 2/6/2016
    Last Modified: 5/18/2017

.EXAMPLE   
    $response = .\Get-LifxAPI.ps1 -Action ListLights
    ---------------------------------------------------------------

    1. Get an object returned that contains the result of the API call
    
    p$[18:52:20]> $response
    id                 : nun-ya
    uuid               : more nun-ya
    label              : Ninja room
    connected          : True
    power              : on
    color              : @{hue=249.9977111467155; saturation=0; kelvin=3400}
    brightness         : 0.48508430609597925
    group              : @{id=nun-ya; name=My Room}
    location           : @{id=nun-ya; name=My Home}
    product            : @{name=Original 1000; identifier=lifx_original_a21; company=LIFX; capabilities=}
    last_seen          : 2017-05-19T01:51:49Z
    seconds_since_seen : 32

.EXAMPLE   
    .\Get-LifxAPI.ps1 -Action RandomColors
    ---------------------------------------------------------------

    1. Light toggles between random colors 10 times

.EXAMPLE   
    .\Get-LifxAPI.ps1 -Toggle
    ---------------------------------------------------------------

    1. Toggles light state (on or off respectively)

.EXAMPLE   
    .\Get-LifxAPI.ps1 -Action SetState -State on -Color blue -Brightness 0.5
    ---------------------------------------------------------------

    1. Turns light on
    2. Changes color to blue
    3. Changes brightness to 0.5
.OUTPUTS
    If you use ListLights as the action, the output will be an
    object containing the results of the api call.

.LINK  
    http://www.gngrninja.com/script-ninja/2016/2/6/powershell-control-your-lifx-light
#>
[cmdletbinding(DefaultParameterSetName='SetState')]
param(
    [Parameter(
        ParameterSetName = "StateSetting"
    )]        
    [ValidateSet('SetState','FlashColor','RandomColors','ListLights')]    
    [string]
    $Action = 'SetState',
    [Parameter(
        ParameterSetName = "StateSetting"
    )]    
    [ValidateSet('on','off')]
    [string]
    $State = 'on',
    [Parameter(
        ParameterSetName = "StateSetting"
    )]    
    [ValidateSet('white','red','orange','yellow','cyan','green','blue','purple','pink')]
    [string]
    $Color = 'white',
    [Parameter(
        ParameterSetName = "StateSetting"
    )]    
    [ValidateRange(0.0,1)]
    [double]
    $Brightness = '0.4',
    [Parameter(
        ParameterSetName = "Toggle"    
    )]    
    [Switch]
    $Toggle
)

$apiKey          = 'YourKeyGoesHere'
$headers         =  @{Authorization=("Bearer {0}" -f $apiKey)}
$allURL          = 'https://api.lifx.com/v1/lights/all' 
$acceptHeader    = 'application/json'
$baseURL         = 'https://api.lifx.com/v1/lights'
$foregroundColor = 'white'
[array]$colors   = @('white','red','orange','yellow','cyan','green','blue','purple','pink')

function Set-LightState { #Begin function Set-LightState
    [cmdletbinding()]
    param(
        [Parameter()]
        [ValidateSet('on','off')]
        [string]
        $state,
        [Parameter()]  
        [string]
        $color,
        [Parameter()]
        [ValidateRange(0.0,1)]
        [double]
        $brightness,
        [string]
        $selector
    )

    if ($lifxLight.Connected) {          

        $fullURL  = "$baseurl/$($lifxLight.id)/state"             
        $payload  = [PSCustomObject]@{

            power      = $state
            color      = $color
            brightness = $brightness

        }

        $payloadJson = $payload | ConvertTo-Json

        Write-Host "Attempting to Change light to:" -ForegroundColor $foregroundcolor
        Write-Host `t"State     :" $state
        Write-Host `t"Color     :" $color
        Write-Host `t"Brightness:" ($brightness * 100)"%" `n

        $stateReturn = Invoke-RestMethod -headers $headers -uri $fullURL -Method Put -Body $payloadJson -ContentType $acceptheader
    
        Write-Host "API status:" -ForegroundColor $foregroundcolor
        Write-Host `t"Light :" $stateReturn.results.label
        Write-Host `t"Status:" $stateReturn.results.status `n
    
    } else {
        
        $lightName = $lifxLight.label
        Write-Host "$lightName is offline or unreachable :("
        
    }    

} #End function Set-LightState

#Get Lifx Lights
$lifxLights = Invoke-RestMethod -headers $headers -Uri $allURL -ContentType $acceptheader
if ($lifxLights.Count -gt 1) {

    Write-Host "Pick a light:"`n
    for ($i = 0;$i -le $lifxLights.Count - 1;$i++) {

        Write-Host "[$i] -> $($lifxLights[$i].label)"

    }

    $picked = $false

    Do {
        [int]$number = Read-Host "Which [#]?"
        Write-Host "$number"
        if (![String]::IsNullOrEmpty($number) -and ($number -le ($lifxLights.Count - 1) -and $number -gt -1)) {

            $picked    = $true
            $lifxLight = $number  
            write-Host "sup"

        }
    } 
    While ($picked -eq $false)

} else {

    $lifxLight = $lifxLights[0]

}

if ($toggle) { #If we're just toggling
             
    $fullURL  = "$baseurl/$($lifxLight.id)/toggle"  
    $toggleResult = Invoke-RestMethod -Uri $fullURL -Method Post -ContentType $acceptHeader -Headers $headers

    if ($toggleResult.results.status -eq 'ok') {

        Write-Host "Light [$($toggleResult.results.label)] was toggled!"

    } else {

        Write-Host "Something went wrong: [$($toggleResult.results.status)]"
    }

} else { #Else we perform the actions!

    Switch ($action) { #Begin switch for script actions
        
        'SetState' {         
            
            Set-LightState -state $state -color $color -brightness $brightness

        }    
        
        'ListLights' {
            
            return $lifxLight
            
        }       

        'RandomColors' { 
            
            $originalBrightness = $lifxLight.Brightness
            $originalColor      = $lifxLight.Color
            $originalState      = $lifxLight.Power
            $colorString        = "hue:" + $originalcolor.hue + " saturation:" + $originalcolor.saturation + " kelvin:" + $originalColor.Kelvin       
            
            $i = 0
            
            While ($i -le 10) {
                
                $color              = Get-Random $colors
                [double]$brightness = "{0:n2}" -f (Get-Random -Maximum 1 -Minimum 0.00)
    
                Set-LightState -color $color -brightness $brightness -selector $selector -state $state
                Start-Sleep -seconds 1
                $i++
                
            }
            
            Set-LightState -state $originalState -color $colorString -brightness $originalBrightness

        }
        
        'FlashColor' {
            
            $originalBrightness = $lifxLights.Brightness
            $originalColor      = $lifxLights.Color
            $originalState      = $lifxLights.Power
            $colorString        = "hue:" + $originalcolor.hue + " saturation:" + $originalcolor.saturation + " kelvin:" + $originalColor.Kelvin
            
            Set-LightState -state $state -color $color -brightness $brightness
            Start-Sleep -Seconds 1
            
            Set-LightState -state $originalState -color $colorString -brightness $originalBrightness
            Start-Sleep -Seconds 1
                    
            Set-LightState -state $state -color $color -brightness $brightness
            Start-Sleep -Seconds 1
            
            Set-LightState -state $originalState -color $colorString -brightness $originalBrightness 
            Start-Sleep -Seconds 1        
            
            Set-LightState -state $state -color $color -brightness $brightness
            Start-Sleep -Seconds 1
            
            Set-LightState -state $originalState -color $colorString -brightness $originalBrightness
                            
        }    
    
    } #End switch for light actions
}

Leave comment if you have any ideas or feedback!

-Ginger Ninja

PowerShell: Fun with Weather Underground's API (part 1)

PowerShell: Fun with Weather Underground's API (part 1)

Free APIs can be fun!

After doing some work with the Victor OPs API, I personally wanted to see what else was out there. I haven't messed with APIs too much in the past, but now I'm all about it. I'm a bit of a weather geek so I was excited when I found out that Weather Underground has one available for free!

What I've done so far

So far I've created a script that can:

  • Email you an HTML email with the following:
    • Hourly/4 day weather forecast
    • Current (randomly selected) webcam shot from the city you specified
    • Radar animated gif
  • Email a Squarespace gallery with a random camera shot from the city you specify.
  • Return (in PowerShell) the forecast for the city you specify

I'm thinking of things to add to it all the time now... like perhaps an alert check on an interval that sends you a text if there is a weather alert.

I'm doing all this to learn the ins and outs of handling APIs with PowerShell, as well as to feed my inner weather and script geek.

Getting started

This post will be a multi part post. For this portion I will stick to the part that you don't even need and API key for!  (Although, you may want to grab one, here)

Requirements

You'll need to setup the following variables:

  • $apiKey (Can be blank now or just 'yourAPIKeyHere') as it is not actually needed yet.
  • $city (Specify a city you'd like to look up using their free autocomplete API)
  • $baseURL  = 'http://api.wunderground.com/api/'

The Code

$city    = Read-Host "City?"
$apiKey  = 'yourAPIKeyHere' 
$baseURL = 'http://api.wunderground.com/api/'

   $findMe = $city
    $find   = Invoke-RestMethod -Uri "http://autocomplete.wunderground.com/aq?query=$findMe"
    
    if ($find) {
        
        $cityAPI  = $find.Results[0].l
        $city     = $find.Results[0].name
        
        $fullURL  = $baseURL + $apiKey + "/features/conditions/hourly/forecast/webcams/alerts" + "$cityAPI.json"
        $radarURL = "http://api.wunderground.com/api/$apiKey/animatedradar/animatedsatellite" + "$cityAPI.gif?num=6&delay=50&interval=30"
        
        Write-Host `n"API URLS for $city" -foregroundcolor $foregroundColor
        Write-Host `t$fullURL
        Write-Host `t$radarURL

}

Results / Going forward

If you put the above code in a script and run it, the results will be the API URLs it found for the first city it returns. If you want to narrow down it for a different $city, you'll need to specify 'City, State'.

Here are the results for $city = 'Portland'

If you want to get a jump start on this and you have your API key, check out the API documentation from Weather Underground!

Here is a teaser of what can be done (all via PowerShell!):

 

Let me know if you have any ideas or comments!

-Ginger Ninja

PowerShell: Reset a user's AD password

PowerShell: Reset a user's AD password

I cant login

"My password isn't working!" ...  Can you reset it?" These words are typically heard at least once or twice a day by front line support at most places. No matter how much we try to inform and remind users about their expiring passwords, there will be those that forget to change it before it expires or end up locking themselves out one way or another (iPhone/Exchange anyone?)

AD functions in PowerShell

I have written a few AD functions in PowerShell. One of them gets information about a user and then passes that along to possible actions to take. The function I'm writing about is one of those actions. 

Requirements

  • Quest AD tools
  • Resolve-UserName function (will give the code for that below)
  • Set $foregroundColor to something you like so it looks pretty.

Actions!

What this function will do is take an account name (or ask you for one), and then give you 3 options.

  • Reset and flag for change
  • Reset and don't flag for a change
  • Just flag for reset on next logon (or unflag if already flagged)

Parameters

-actName (Account name to reset)

The Code

This is the code for the Resolve-Username function:

function Resolve-UserName {

    param ($Username, [switch]$Verbose=$false)

    if (!$Username) {
        $Username = Read-Host "Enter username" 
    }

    if($Verbose){ Write-Host "Attempting to resolve `"$Username`"..." }
    $ResolvedName = Get-QADUser $Username -IncludeAllProperties
    do{
        If ($ResolvedName.Length -gt 1) {
    
            Write-Host `n"Multiple users found for `"$Username`", please select the intended user."`n -ForegroundColor $foregroundColor
            
            $i = 0
    
            Foreach ($u in $ResolvedName) {
                Write-Host "$i -> " $u.Name
                $i++
            }
            $selUsr = Read-Host "Which #?"
            $ResolvedName = Get-QADUser $ResolvedName[$selUsr] -IncludeAllProperties
        }
        elseif($ResolvedName.Length -eq 0){
            Write-Host "User does not exist. Try again." -ForegroundColor Red
            $ResolvedName = Get-QADUser (Read-Host "Enter username") -IncludeAllProperties
        }
    }until($ResolvedName.length -eq 1)
    if($Verbose){ Write-Host "Resolved `"$Username`" to" $ResolvedName.SamAccountName }
    Return $ResolvedName
}

This is the code for the Reset-ADPassword function:

function Reset-ADPassword {

    param($actName)
    
    if(!$actName){$actName = Read-Host "Enter username"}
    $usrObj = Resolve-UserName $actName

    If ($usrObj) {    
        
        Write-Host `n"Password reset:" $usrObj.Name -ForegroundColor $foregroundColor
        
        Write-Host "1. Reset and flag"
        Write-Host "2. Reset and do not flag"
        Write-Host "3. Just flag (or unflag if flagged)"`n
        
    
        $psdOp = Read-Host "Please specify an option #" 
        Write-Host "Note: You will be prompted to confirm any option you select." -ForegroundColor $foregroundColor
    
        Switch ($psdOp)  {
    
            1 { 
                
                $resetTo = Read-Host "Reset password to" -AsSecureString
                $resetTo = $resetTo | ConvertFrom-SecureString
                $PlainTextPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR( (ConvertTo-SecureString $resetTo) ))
                Set-QADUser $usrObj -UserPassword $PlainTextPassword -UserMustchangePassword $true -Credential $adminCredential -confirm }
    
            2 { 
                
                $resetTo = Read-Host "Reset password to" -AsSecureString
                $resetTo = $resetTo | ConvertFrom-SecureString
                $PlainTextPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR( (ConvertTo-SecureString $resetTo) ))
                Set-QADUser $usrObj -UserPassword $PlainTextPassword -UserMustchangePassword $false -Credential $adminCredential -confirm }
    
            3 { 
                
                if ($usrObj.UserMustChangePassword -eq $true) {
                    Set-QADUser $usrObj -UserMustChangePassword  $false  -Credential $adminCredential -confirm }
                else { Set-QADUser $usrObj -UserMustchangePassword $true -Credential $adminCredential -confirm } }  
        }
    } 
}

Let me know if you have any questions or comments!

 

PowerShell Quick Tip: Basic use of the Switch statement

PowerShell Quick Tip: Basic use of the Switch statement

Why use Switch?

In PowerShell you can use an if statement to compare things and determine actions based upon the result. You also can use elseif to keep comparing values until you reach a desired (or not so desired) result.

Switch allows you to take a variable you define, and then based upon its value perform different actions.

Example

In this example I will show you a switch statement I used in a script that uses the Weather Underground API to send HTML emails detailing your requested weather forecast. Consider this a teaser until I post that script itself.

What I wanted to do

In this example I wanted to take a returned percentage, and then change a variable to display the appropriate text result. It compares the returned value of the hour's chance of precipitation set in $hourPrecip, and then based on that number changes the variable $popText accordingly.

The Code

Switch ($hourPrecip) {
                        
                        {$_ -lt 20} {
                            
                            $popText = 'No mention'
                            
                        }
                        
                        {$_ -eq 20} {
                            
                            $popText = 'Slight Chance'
                            
                        }
                        
                        {($_ -lt 50 -and $_ -gt 20)} {
                            
                            $popText = 'Chance'
                            
                        }
                        
                        {$_ -eq 50} {
                            
                            $popText = 'Good chance'
                            
                        }
                        
                        {($_ -lt 70 -and $_ -gt 50)} {
                            
                            $popText = 'Likely'
                            
                        }
                        
                        {$_ -ge 70} {
                            
                            $popText = 'Extremely likely'
                            
                        }
                        
                    }

So now you display the percentage, as well as its correlating text value which describes what the percentage means.

If the value was 51 for $hourPrecip, then $popText will be set to Likely

The Weather Underground post will be coming soon!

Do you use switches in your scripts, and if so, how do you like to use them?

PowerShell: Building a basic text menu system (part 1)

PowerShell: Building a basic text menu system (part 1)

Menus?! But...why?

As I dove deeper into PowerShell years ago I started to think about ways to help those utilizing my scripts. I wrote a script that became a module that the whole team would use.

In this module there were a ton of functions. Some functions were for creating new hires, others for terminations, and even more for general AD user account and Exchange management. Once there were more than 6 different functions I created a basic menu system for the Helpdesk to use when using PowerShell with my module.

Where to start

The first thing you'll need to do is present the user with options. Write-Host is my favorite way to do this. You'll want to define the variable $foregroundColor to see the full effect of the following code. I like to keep that set as a global variable so it can be changed later if desired. This is of course, very important, as who doesn't want to sometimes tweak the color output of your scripts? 

I also keep a version number in the variable $ncVer.

Building the menu:

Write-Host `n"Ninja Center v" $ncVer -ForeGroundColor $foregroundcolor
Write-Host `n"Type 'q' or hit enter to drop to shell"`n
Write-Host -NoNewLine "<" -foregroundcolor $foregroundColor
Write-Host -NoNewLine "Active Directory"
Write-Host -NoNewLine ">" -foregroundcolor $foregroundColor
Write-Host -NoNewLine "["
Write-Host -NoNewLine "A" -foregroundcolor $foregroundColor
Write-Host -NoNewLine "]"

Write-Host -NoNewLine `t`n "A1 - " -foregroundcolor $foregroundcolor
Write-host -NoNewLine "Look up a user"
Write-Host -NoNewLine `t`n "A2 - " -foregroundcolor $foregroundcolor
Write-host -NoNewLine "Enter PowerShell session on DC"
Write-Host -NoNewLine `t`n "A3 - " -foregroundcolor $foregroundcolor
Write-host -NoNewLine "Run DCDiag on a DC (or all DCs)"`n`n

Write-Host -NoNewLine "<" -foregroundcolor $foregroundColor
Write-Host -NoNewLine "Exchange"
Write-Host -NoNewLine ">" -foregroundcolor $foregroundColor
Write-Host -NoNewLine "["
Write-Host -NoNewLine "E" -foregroundcolor $foregroundColor
Write-Host -NoNewLine "]"

Write-Host -NoNewLine `t`n "E1 - " -foregroundcolor $foregroundcolor
Write-host -NoNewLine "Forward a mailbox"
Write-Host -NoNewLine `t`n "E2 - " -foregroundcolor $foregroundcolor
Write-host -NoNewLine "Clear a mailbox forward"
Write-Host -NoNewLine `t`n "E3 - " -foregroundcolor $foregroundcolor
Write-host -NoNewLine "See if an IP address is being relayed"`n`n

Write-Host -NoNewLine "<" -foregroundcolor $foregroundColor
Write-Host -NoNewLine "Storage"
Write-Host -NoNewLine ">" -foregroundcolor $foregroundColor
Write-Host -NoNewLine "["
Write-Host -NoNewLine "S" -foregroundcolor $foregroundColor
Write-Host -NoNewLine "]"

Write-Host -NoNewLine `t`n "S1 - " -foregroundcolor $foregroundcolor
Write-host -NoNewLine "Connect to a NetApp controller"`n`n

When starting out I would use Write-Host to display information as I wrote scripts. This was nice, and was useful for troubleshooting. Then as my knowledge expanded I began to realize how else it worked. My new favorite, especially when building menus, is -NoNewLine. 

What -NoNewLine allows you to do is alternate different colors on the same line. Again, extremely uhhh... important. The output does look nice, though!

There are also some escaped string options I like to use. They are `t (horizontal tab), and -n(new line). These are used to keep the menu looking nice and not too cluttered.

OK so I built something that shows a bunch of stuff... what now?

Now we'll need to get some input from the user and take action! To do this we'll use Read-Host combined with a Switch to deal with the input.

$sel = Read-Host "Which option?"

Switch ($sel) {
    "A1" {Get-ADinfo;Load-NinjaCenter}
    "A2" {Enter-DCSession}
    "A3" {
        $DCs      = Read-Host "DC (specify name or put 'all' for all)?"
        $test     = Read-Host "Enter 'error' or 'full' for test feedback"         
       
        
        $global:dcDiagResults = Get-DCDiagInfo -DC $DCs -Type $test -Verbose
        
        Write-Host `n"Results stored in the variable: dcDiagResults"`n
        Write-Host -NoNewLine "Type "
        Write-Host -NoNewLine "Load-NinjaCenter " -foregroundcolor $foregroundcolor
        Write-Host -NoNewLine "to load the menu again."`n
    }
    
    "E1" {Forward-Email}
    "E2" {Clear-Forward}
    "E3" {Check-EXRelayIP}
    
    "S1" {
        Connect-NetAppController
    
        Write-Host -NoNewLine "Type "
        Write-Host -NoNewLine "Load-NinjaCenter " -foregroundcolor $foregroundcolor
        Write-Host -NoNewLine "to load the menu again."`n
}

    {($_ -like "*q*") -or ($_ -eq "")} {
        
        Write-Host `n"No input or 'q' seen... dropping to shell" -foregroundColor $foregroundColor
        Write-Host "Type Load-NinjaCenter to load them menu again"`n
        
        
    }          
        
}

Alright, now let's take some action!

As you can see above, the switch handles the user input. There are some more advanced ways to parse the input, which I will go over in a later post. For now, you can see a few things.

One is that you can stage other functions based on the input. For example, with the switch option "A3", (which is the menu option for running DC Diag against DCs), you can see that we provide the user with some more prompts to prep some variables to pass to the Get-DCDiagInfo function.

I actually wrote about my Get-DCDiagInfo (function, but also works as a standalone script), and you can read about it here.

This allows you to wrap your existing functions in different ways and present them to folks that will use them however you'd like to. For a Helpdesk environment, or those not super deep into PowerShell, this is a nifty way for them to encapsulate and utilize what you've written. 

Let's put it all together.

Here is the full code for the menu function:

function Load-NinjaCenter {  
[cmdletbinding()]
param()

    ##########################################
    #            Ninja Center Menu           #
    ##########################################
    Write-Host `n"Ninja Center v" $ncVer -ForeGroundColor $foregroundcolor
    Write-Host `n"Type 'q' or hit enter to drop to shell"`n
    Write-Host -NoNewLine "<" -foregroundcolor $foregroundColor
    Write-Host -NoNewLine "Active Directory"
    Write-Host -NoNewLine ">" -foregroundcolor $foregroundColor
    Write-Host -NoNewLine "["
    Write-Host -NoNewLine "A" -foregroundcolor $foregroundColor
    Write-Host -NoNewLine "]"

    Write-Host -NoNewLine `t`n "A1 - " -foregroundcolor $foregroundcolor
    Write-host -NoNewLine "Look up a user"
    Write-Host -NoNewLine `t`n "A2 - " -foregroundcolor $foregroundcolor
    Write-host -NoNewLine "Enter PowerShell session on DC"
    Write-Host -NoNewLine `t`n "A3 - " -foregroundcolor $foregroundcolor
    Write-host -NoNewLine "Run DCDiag on a DC (or all DCs)"`n`n

    Write-Host -NoNewLine "<" -foregroundcolor $foregroundColor
    Write-Host -NoNewLine "Exchange"
    Write-Host -NoNewLine ">" -foregroundcolor $foregroundColor
    Write-Host -NoNewLine "["
    Write-Host -NoNewLine "E" -foregroundcolor $foregroundColor
    Write-Host -NoNewLine "]"

    Write-Host -NoNewLine `t`n "E1 - " -foregroundcolor $foregroundcolor
    Write-host -NoNewLine "Forward a mailbox"
    Write-Host -NoNewLine `t`n "E2 - " -foregroundcolor $foregroundcolor
    Write-host -NoNewLine "Clear a mailbox forward"
    Write-Host -NoNewLine `t`n "E3 - " -foregroundcolor $foregroundcolor
    Write-host -NoNewLine "See if an IP address is being relayed"`n`n

    Write-Host -NoNewLine "<" -foregroundcolor $foregroundColor
    Write-Host -NoNewLine "Storage"
    Write-Host -NoNewLine ">" -foregroundcolor $foregroundColor
    Write-Host -NoNewLine "["
    Write-Host -NoNewLine "S" -foregroundcolor $foregroundColor
    Write-Host -NoNewLine "]"

    Write-Host -NoNewLine `t`n "S1 - " -foregroundcolor $foregroundcolor
    Write-host -NoNewLine "Connect to a NetApp controller"`n`n

    $sel = Read-Host "Which option?"

    Switch ($sel) {
        "A1" {Get-ADinfo;Load-NinjaCenter}
        "A2" {Enter-DCSession}
        "A3" {
            $DCs      = Read-Host "DC (specify name or put 'all' for all)?"
            $test     = Read-Host "Enter 'error' or 'full' for test feedback"         
        
            
            $global:dcDiagResults = Get-DCDiagInfo -DC $DCs -Type $test -Verbose
            
            Write-Host `n"Results stored in the variable: dcDiagResults"`n
            Write-Host -NoNewLine "Type "
            Write-Host -NoNewLine "Load-NinjaCenter " -foregroundcolor $foregroundcolor
            Write-Host -NoNewLine "to load the menu again."`n
        }
        
        "E1" {Forward-Email}
        "E2" {Clear-Forward}
        "E3" {Check-EXRelayIP}
        
        "S1" {
            Connect-NetAppController
        
            Write-Host -NoNewLine "Type "
            Write-Host -NoNewLine "Load-NinjaCenter " -foregroundcolor $foregroundcolor
            Write-Host -NoNewLine "to load the menu again."`n
    }

        {($_ -like "*q*") -or ($_ -eq "")} {
            
            Write-Host `n"No input or 'q' seen... dropping to shell" -foregroundColor $foregroundColor
            Write-Host "Type Load-NinjaCenter to load them menu again"`n
            
            
        }          
            
    }
    ##########################################
    #        End Ninja Center Menu           #
    ##########################################

}

What you'd do to run it is put it in a module you created and then call the function when the module is imported at the end of your script. Once exited, it could be called by its function name at any time to bring the menu back up.

Some things I've noticed

When I started sharing my scripts this way I noticed that people will find ways to break your code. This is a great way to learn the weak points of your scripts and become even better at writing them. I hope you've found this useful, and as always I appreciate any feedback!

PowerShell: Using the VictorOps REST API and PRTG Sensor Output

PowerShell: Using the VictorOps REST API and PRTG Sensor Output

What's it do?

I utilized the VictorOps REST API with PowerShell to return data in a user readable format, or to use with PRTG sensors.

I wanted a way to display who was currently on call via a PRTG map that other users can publicly access.

Note: This is something I wrote in a day, and it will be evolving into a cleaner, error checked format!

What you'll need

Parameters

-getInfo

  • ack               = List/ack any outstanding alerts. Will prompt for message and confirm before acknowledging.
  • OnCall         = List who is currently on call (must specify team with -team parameter) (compatible with -format parameter and PRTG sensors)
  • UserSched  = Get a user's oncall schedule. Currently best to return this into your own variable for now. (Must specify -user parameter)
  • Upcoming   = See who is on call for a team in the future. (must specify team with -team parameter)
  • Incident       = List all incidents (compatible with -format parameter and PRTG sensors)

-team

  • Specify the name of the team. If you have not setup the switch information later in this script, do so to ensure it lines up.
     

-user

  • Specify the username you're getting info for.

-format

  • Specify 'prtg' for certain -getInfo parameters and it will dump it as PRTG sensor information. Leave blank or specify 'user' to return a human readable response. Use 'raw' to return the raw API data. It would be best to call this script into a variable when doing this. $rawAPI = .\Script-Name.ps1 -getInfo 'oncall' -team 'teamname' -format 'raw' 

Examples

Here is some example output.

 .\Get-VictorOpsInfo.ps1 -getInfo incidents
.\Get-VictorOpsInfo.ps1 -getInfo incidents -format prtg

Setting it up

You'll need your API Key and ID from VictorOps. Once you have that, be sure to set them in the script.

$apiKey = ''
$apiID= ''

You can generate a key in VictorOps by going to "Settings -> API."

Be sure to change this switch to match your team setup in VictorOps.

Switch ($team) {

{$team -like "*team1*"}{

$teamName ='team1name'

}

{$team -like "*team2*"} {

$teamName = 'team2name'

}

}

Your username will be set to the username you're logged into the machine with. This is important to note as it sends username when you use the script to ack alerts. To change this, edit the following (you'll want to do this if your username does not match the one in VictorOps):

$ackUser = (Get-ChildItem ENV:\USERNAME).Value 

PRTG Setup

To use this and report info into PRTG, setup the script and ensure Quest AD PowerShell tools are installed on the PRTG machine. 

  1. Drop the script in your Custom Sensor folder (Example: D:\PRTG Network Monitor\Custom Sensors\EXEXML\)
  2. Setup an "EXE/Script Advanced" sensor on PRTG and name it something like "VictorOps Incidents". Select the script in the drop-down.
  3. In the settings specify the following parameters: -getInfo incidents -format PRTG

 

You can also setup a sensor for team on call reporting.
To do this, perform the above steps, but specify the parameters -getInfo OnCall -team unix -format prtg. 
 

That will return a sensor that returns a text message of who is on call with their phone number (pulled from AD).

You can then create a map in PRTG of who is on call that can be provided publicly. To get the on call info use the "Status Icons -> Sensor Message only" Properties in the PRTG map builder.

Scanning Interval

Be sure to change the scanning interval in PRTG to something that makes sense. There probably isn't a reason to keep it at every minute, so change it!


The code

<#   
.SYNOPSIS   
   This script utilizes the VictorOps API to return requested information.
.DESCRIPTION 
   This script can be used in various ways. This implementation returns data and interacts with the API to ack alerts. 
   It can also return values which can be used by PRTG sensors.
   
   Variable structure:
    $apiKey       = Your Victor OPs API Key   
    $apiID        = Your Victor OPs API ID
    $baseURL      = The base URL for the VictorOPs API. You shouldn't need to change this. 
    $acceptheader = "application/json" - this default should be fine
    
    The below information is gathered using the above variables.
    
    $headers = @{ 
    'Accept'       = $acceptheader
    'X-VO-Api-Id'  = $apiID
    'X-VO-Api-Key' = $apiKey    
    }
    
    For more info on the VictorOps API, visit https://portal.victorops.com/public/api-docs.html#/.
.PARAMETER getInfo
    Specify the information you'd like returned or action to take.
    
    ack       = List/ack any outstanding alerts
    OnCall    = List who is currently on call (must specify team with -team parameter) (compatible with -format parameter and PRTG sensors)
    UserSched = Get a user's oncall schedule. Best to return this into your own variable for now. (Must specify -user parameter)
    Upcoming  = See who is on call for a team in the future. (must speficy team with -team parameter)
    Incident  = List all incidents (compatible with -format parameter and PRTG sensors)
 
.PARAMETER team
    Specify the name of the team. If you have not setup the switch information later in this script, do so to ensure it lines up.
    
.PARAMETER user
    Specify the username you're getting info for.

.PARAMETER format
    Specify 'prtg' for certain -getInfo parameters and it will dump it as PRTG sensor information.
    Leave blank or specify 'user' to return a human readable response.
    Use 'raw' to return the raw API data. It would be best to call this script into a variable when doing this. $rawAPI = .\Script-Name.ps1 -getInfo 'oncall' -team 'teamname' -format 'raw' 
    
.NOTES   
    Name: Get-VictorOpsInfo.ps1
    Author: Ginger Ninja (Mike Roberts)
    DateCreated: 1/28/16
.LINK  
    http://www.gngrninja.com/script-ninja/2016/1/28/powershell-using-the-victorops-rest-api   
.EXAMPLE   
    .\Get-VictorOpsInfo.ps1 -getInfo OnCall -team unix
 .Example
    .\Get-VictorOpsInfo.ps1 -getInfo ack
#> 
[cmdletbinding()]
param(
    [string]
    $team,
    [string]
    $getInfo = 'notset',
    [string]
    $user,
    [string]
    $format = 'user'
)

if (!((Get-PSSnapin -name "Quest.ActiveRoles.ADManagement" -ea SilentlyContinue).name -eq "Quest.ActiveRoles.ADManagement")) {
  
        Write-Host `n"Loading Quest Active Directory Tools Snapin..."`n -ForegroundColor $foregroundColor
     
        Add-PSSnapin Quest.ActiveRoles.ADManagement | Out-Null
    }  if(!$?) {

        Write-Host `n"You need to install Quest Active Directory Tools Snapin from http://software.dell.com/products/activeroles-server/powershell.aspx"`n -ForegroundColor Red

        Start-Process "http://software.dell.com/products/activeroles-server/powershell.aspx"
        Break;
}

$scriptName   = $myInvocation.MyCommand
$path         = Split-Path -Path $MyInvocation.MyCommand.Path
$apiKey       = 'yourAPIKey'
$apiID        = 'Your API ID'
$baseURL      = 'https://api.victorops.com' 
$acceptheader = "application/json"

$headers = @{
    'Accept'       = $acceptheader
    'X-VO-Api-Id'  = $apiID
    'X-VO-Api-Key' = $apiKey
}

function Get-OnCallSchedule {
    [cmdletbinding()]
    param(
        [string]
        $baseURL,
        [string]
        $onCallURL
    )
    

    $fullURL = $baseURL + $onCallURL
    
    if ($fullURL) {
        
        $onCallSchedule = Invoke-RestMethod $fullURL -Headers $headers
        
    }
    
    return $onCallSchedule
    
}

function Get-Incident {
    [cmdletbinding()]
    param($baseURL)
    
    $incidentURL     = '/api-public/v1/incidents'
    $fullURL         = $baseURL + $incidentURL
    $onCallIncidents = (Invoke-RestMethod $fullURL -Headers $headers).Incidents

    Return $onCallIncidents
    
}

function Resolve-Incident {
    [cmdletbinding()]
    param ($baseURL)

    $incidents = Get-Incident $baseURL
    
    $i = 0
    #Switch for current phase?
    foreach ($in in $incidents) {
        
        if ($in.currentPhase -like 'UNACKED') {
            
            $i++
            
            $ackURL  = "/api-public/v1/incidents/ack"
            
            $fullURL = $baseURL + $ackURL
            
            Write-Host $in.EntityDisplayName
            Write-Host $in.incidentNumber
            
            $inName = $in.EntityDisplayName
            $inNum  = $in.incidentNumber
            
            Write-Host $fullURL
            
            if ((Read-Host "Ack this alert?") -like "*y*") {       
    
                $ackUser = (Get-ChildItem ENV:\USERNAME).Value  
                $ackMsg  = Read-Host "Ack message?"    
                $outputString = $null
                $outputString = "{
                `"userName`": `"$ackUser`",
                `"incidentNames`": [
                `"$inNum`"
                ],
                `"message`": `"$ackMsg`"
                }"

                Invoke-RestMethod -URi $fullURL -Method Patch -contentType "application/json" -Headers $headers -body $outputString
            
            }
           
            } elseif ($in.currentPhase -like 'ACKED') {
                
                $i++
                
                $ackURL  = "/api-public/v1/incidents/resolve"
                
                $fullURL = $baseURL + $ackURL
                
                Write-Host $in.EntityDisplayName
                Write-Host $in.incidentNumber
                
                $inName = $in.EntityDisplayName
                $inNum  = $in.incidentNumber
                
                if ((Read-Host "Resolve this alert?") -like "*y*") {       
        
                    $ackUser = (Get-ChildItem ENV:\USERNAME).Value  
                    $ackMsg  = Read-Host "Resolution message?"    
                    $outputString = $null
                    $outputString = "{
                    `"userName`": `"$ackUser`",
                    `"incidentNames`": [
                    `"$inNum`"
                    ],
                    `"message`": `"$ackMsg`"
                    }"

                    Invoke-RestMethod -URi $fullURL -Method Patch -contentType "application/json" -Headers $headers -body $outputString
                 
                } 
           }   
    }
    
    if ($i -eq 0) { Write-Host "No alerts to ack or resolve!" }
    
}


Switch ($team) {
  
    {$team -like "*team1*"}  {
        
        $teamName ='team1fullname'
    
    }

    {$team -like "*team2*"} {
        
        $teamName = 'team2fullname'
    
    }  
    
}

Switch ($getInfo) {
    
    {$_ -like "OnCall"} {
       
        if (!$team) {Write-Host `n"Team not specified!"`n -foregroundcolor Red;break}
        
        $onCallURL      = "/api-public/v1/team/$teamName/oncall/schedule"
        $onCallSchedule = Get-OnCallSchedule $baseurl $oncallurl  
        
        if (!$onCallSchedule.Schedule.overrideOnCall) {
    
            $onCallUser     = Get-QADuser $onCallSchedule.Schedule.onCall
           
        } else {
            
             $onCallUser     = Get-QADuser $onCallSchedule.Schedule.overrideOnCall
            
        }        
        
        if ($onCallUser) {
           
            $name   = $onCallUser.Name
            $mobile =  $onCalluser.MobilePhone
        
            Switch ($format) {
         
                {$_ -eq 'user'} {
                 
                    Write-Host `n"Currently on call for team: $team"
                    Write-Host $name "($mobile)"`n
                 
                }
             
                {$_ -eq 'prtg'} {
             
                    Write-Host "<prtg>"                     
                    Write-Host
                    "<result>"
                    "<channel>Team: $team</channel>"
                    "<value>1</value>"
                    "</result>" 
                    "<text>$name ($mobile)</text>"
                    Write-Host "</prtg>"
                    
                }
                
                {$_ -eq 'raw'} {
             
                    Return $onCallSchedule
                    
                }           
             }
         
         }
    }  
  
  {$_ -like "*Upcoming*"} {

        if (!$team) {Write-Host `n"Team not specified!"`n -foregroundcolor Red;break}
        
        $onCallURL      = "/api-public/v1/team/$teamName/oncall/schedule"
        $onCallSchedule = Get-OnCallSchedule $baseurl $oncallurl  
        $upcoming       = $onCallSchedule.schedule.rolls
        
        $upcoming | ForEach-Object{$WeekOf = $_.change.SubString(0,10);$Who = (Get-QADUser $_.OnCall).Name; Write-Host $WeekOf $Who}
          
  } 
  
  {$_ -like "*incidents*"} {
      
        $incidents = Get-Incident $baseURL
        #Maybe make this a switch...
        $unAcked   = $incidents | Where-Object{$_.currentPhase -eq 'UNACKED'}
        $unRes     = $incidents | Where-Object{$_.currentPhase -eq 'ACKED'}
        $resAlerts = $incidents | Where-Object{$_.currentPhase -eq 'RESOLVED'}
        
        if ($unAcked) {
            
            $counta = 1
            if ($unAcked.Count) {$counta = $unAcked.Count}
            $texta    = $unAcked.entityDisplayName 
          
        } else {$counta = 0;$texta = "No alarms in VictorOps!" }
        
        if ($unRes) {
            
            $countr = 1
            if ($unRes.Count) {$countr = $unRes.Count}
            $texta    = "Alert list: " + $unRes.entityDisplayName 
          
        } else {$countr = 0}  
              
        if ($resAlerts) {
       
            $countre = 1
            if ($resAlerts.Count) {$countre = $resAlerts.Count} 
          
        } else {$countre = 0}          
        
        Switch ($format) {
            
            {$_ -eq 'prtg'} {
                
                Write-Host "<prtg>"    
                                 
                Write-Host
                "<result>"
                "<channel>Unacknowledged Alerts</channel>"
                "<value>$counta</value>"
                "</result>" 
                "<text>$texta</text>"
            
                Write-Host
                "<result>"
                "<channel>Unresolved Alerts</channel>"
                "<value>$countr</value>"
                "</result>" 
                "<text>$textr</text>"
        
                Write-Host
                "<result>"
                "<channel>Resolved Alerts</channel>"
                "<value>$countre</value>"
                "</result>" 
                "<text>$textre</text>"
                
                Write-Host "</prtg>"        
                     
            }
            
            {$_ -eq 'user'} {
                
                Write-Host `n"Unacknowledged Alerts" 
                Write-Host `t$counta `n -foregroundcolor Red
                
                Write-Host "List:"`n
                
                foreach ($alert in $unAcked) {
                
                   Write-Host `t $alert.entityDisplayName `n
                
                }                
                
                Write-Host "Unresolved Alerts" 
                Write-Host `t$countr `n -foregroundcolor Red

                Write-Host "List:"`n
                
                foreach ($alert in $unRes) {
                
                   Write-Host `t $alert.entityDisplayName `n
                
                }

                
                Write-Host "Resolved Alerts" 
                Write-Host `t$countre`n -foregroundcolor Green
                
                Write-Host "List:"`n
                
                foreach ($alert in $resAlerts) {
                
                   Write-Host `t $alert.entityDisplayName `n
                
                }
            }
            
            {$_ -eq 'raw'} {
                
                Return $incidents 
             
            }            
            
        }

        
  }
  
  {$_ -like "*usersched*"} {
      
        if ($user) {
      
            $onCallURL      = "/api-public/v1/user/$user/oncall/schedule"
            $onCallSchedule = Get-OnCallSchedule $baseurl $oncallurl
            Return $onCallSchedule

        } else {Write-Host `n"User not specified!"`n -foregroundcolor Red}
  }
  
  {$_ -like "ack"} {
      
      Resolve-Incident $baseURL
      
  }
   
  {$_ -eq 'notset'} {
      
      Write-Host `n"Please specify the action you'd like to take via the -getInfo parameter" -foregroundColor Red
      Write-Host "For help type Get-Help $path\$scriptName"`n
      
  } 
                    
}

Have any ideas on how to use this, or general feedback? Leave a comment!

 

PowerShell: Check for NetApp failed and unowned disks (CDOT)

PowerShell: Check for NetApp failed and unowned disks (CDOT)

Why I needed this

I received a call from a technician in another site one day to let me know that we had 3 amber lights in our NetApp cluster. While autosupport should have caught this, it didn't (one controller showed them pre-failed, and the other showed them as failed disks).

So I turned to my love of PowerShell and added to the NetApp functions I wrote that perform actions when connected to NetApp controllers. If you haven't seen those posts, check them out!

How to use this

You'll need to first be connected to a NetApp controller and have the NetApp PowerShell module installed. Once that part is completed, you can use the code below. I also like to set a global variable for $foregroundColor, and this script utilizes that.

The Code

$netAppSystemInfo = Get-NCNode

        Write-Host `n"Controller information (CDOT)"`n -ForegroundColor $foregroundColor

        foreach ($node in $netappSystemInfo) {
            
            Write-Host "Node" $node.Node ` -foregroundcolor $foregroundcolor
            Write-Host "Model   :" $node.NodeModel
            Write-Host "Serial #:" $node.NodeSerialNumber
            Write-Host "Location:" $node.NodeLocation
            Write-Host "Healthy?:" $node.IsNodeHealthy
            Write-Host "Uptime  :" $node.NodeUptime
            Write-Host "ONTAPver:" $node.ProductVersion `n
            
        }
$failedDisks = Get-NcDisk | ?{ $_.DiskRaidInfo.ContainerType -eq "broken" }
        
        if ($failedDisks) {
        
            Write-Host `n"There are failed disks on this NetApp cluster!" -foregroundColor Red
            Write-Host "Failed Disks:"`n -foregroundColor $foregroundcolor
            
            foreach ($disk in $failedDisks){
                
                Write-Host "Disk Name      :" $disk.DiskName
                Write-Host "Disk UID       :" $disk.DiskUID
                Write-Host "Disk Controller:" $disk.NcController
                Write-Host "Disk Bay       :" $disk.Bay
                Write-Host "Disk Shelf     :" $disk.Shelf
                Write-Host "Disk model/FW  :" $disk.Model "/" $disk.FW `n
                
                
            }
            
            $openCase = Read-Host "Would you like to open a support case?"
            if ($openCase -like "*y*") {
                
                Start-Process "http://mysupport.netapp.com/cssportal/faces/oracle/webcenter/portalapp/pages/css/casesparts/CreateCaseLanding.jspx?_adf.no-new-window-redirect=true"
                
            }
            
        }
        
        
        $unOwnedDisks =  get-NCDisk
        
        foreach ($d in $unOwnedDisks) { 

            if ($d.diskownershipinfo.OwnerNodeName -eq $null) {
                
                Write-Host `n"Unowned disks found!" -foregroundColor Red
                Write-Host "Unowned Disks:"`n -foregroundcolor $foregroundColor
                Write-Host "Disk Name:" $d.DiskName
                
                $setOwner = Read-Host "Set owner to one of the nodes in the cluster?"
                
                if ($setOwner -like "*y*") {
                    
                    $i = 0 
                    foreach ($node in $netAppSystemInfo) {
                        
                        Write-Host $i -> "Node:" $node.Node
                        $i++
                    
                }
                
                $node = Read-Host "Which node #?"
                $nodeSystemID = $netAppSystemInfo[$node].NodeSystemID
                $nodeName = $netAppSystemInfo[$node].node
                $diskname = $d.DiskName
                $confirm = Read-Host "Set disk owner for disk $diskName to:" $nodeName "with systemID" $nodeSystemID"?"
                
                if ($confirm -like "*y*") {
                    
                    Set-NCDiskOwner -DiskName $diskName -NodeId $nodeSystemID
                    
                }
                    
                    
                    
                }

            } 
        
    
        }

What it will do

It will hunt down any failed or unowned disks. It will then prompt you to open the support page to open a new case. If it finds any unowned disks, it will give you the option to assign them to the controller of your choice.

As always, let me know if you have any feedback!

-Ginger Ninja

 

PowerShell: Email yourself a workout routine

PowerShell: Email yourself a workout routine

I've been into fitness for quite some time. Personally, for logging my workouts I love to use Fitocracy. As for finding my rep max and getting back into a workout routine... I took a geekier approach.

Why and what it taught me

This script was my way to learn how to do some math in PowerShell. From percentages to basic rounding, and then some. I wanted to do the rounding so the "plate" math made more sense.

Here are some ways to run the script:

.\531.ps1 -Max 145 -Reps 10

Running it like this will give you an output in the console of what your 5/3/1 routine at this max and rep range would be.

Another way to run it would be:

.\531.ps1 -Max 145 -Reps 10 -sendEmail email@address.com -workoutName Squats

The above would send the routine to that email address with the workout name in the subject line.

What you'll need to setup

If you want to use the email portion of this script you'll want to change the variables $emailUser, $emailFrom, $emlSignature, and if you're not using Gmail $SMTPServer.

You can use the line that's commented out to store your password locally, machine key encrypted.

#"password" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString | Out-File .\emlpassword.txt

The Code

# Let's get our variables set
[cmdletbinding()]
param (
    [int]
    $Max,
    [int]
    $Reps,
    [double]
    $TMp,
    [string]
    $sendEmail,
    [string]
    $workoutName
)

if (!$Max)     {[int]$Max      = read-host "Weight?"}
if (!$Reps)    {[int]$Reps     = read-host "for how many reps?"}
if (!$TMp)     {[double]$TMp = .85}


#"password" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString | Out-File .\emlpassword.txt

$emailPass    = Get-Content .\emlpassword.txt | ConvertTo-SecureString  

#Setup the email username and from address.
$emailUser    = 'emailuser'
$emailFrom    = 'youremail@gmail.com'
$emlSignature = '-Your Name'
$SMTPServer   = 'smtp.gmail.com'
$tmPretty     = $TMp * 100
$tmPretty     = $tmpretty.ToString() + "%"
[int]$roundBy = 10 


#Numbers boys! (and girls too...)
[int]$weight   = ([system.math]::round(($max * $reps * 0.0333 + $max)/$roundBy))*$roundBy 
[int]$tm       = ([system.math]::round(($tmp * $weight)/$roundBy))*$roundBy
[int]$warm     = ([system.math]::round((.10 * $tm)/$roundBy))*$roundBy

#Work sets
[int]$work1    = ([system.math]::round((.65 * $tm)/$roundBy))*$roundBy
[int]$work2    = ([system.math]::round((.75 * $tm)/$roundBy))*$roundBy
[int]$work3    = ([system.math]::round((.85 * $tm)/$roundBy))*$roundBy
[int]$work12   = ([system.math]::round((.70 * $tm)/$roundBy))*$roundBy
[int]$work22   = ([system.math]::round((.80 * $tm)/$roundBy))*$roundBy
[int]$work32   = ([system.math]::round((.90 * $tm)/$roundBy))*$roundBy
[int]$work13   = ([system.math]::round((.75 * $tm)/$roundBy))*$roundBy
[int]$work23   = ([system.math]::round((.85 * $tm)/$roundBy))*$roundBy
[int]$work33   = ([system.math]::round((.95 * $tm)/$roundBy))*$roundBy
[int]$bbbfwks  = ([system.math]::round((.40 * $tm)/$roundBy))*$roundBy
[int]$bbbfwks2 = ([system.math]::round((.50 * $tm)/$roundBy))*$roundBy

#Warm up sets
[int]$wset1    = $work1 - $warm
[int]$wset2    = $wset1 - $warm
[int]$wset3    = $wset2 - $warm
[int]$wset12   = $work12 - $warm
[int]$wset22   = $wset12 - $warm
[int]$wset32   = $wset22 - $warm
[int]$wset13   = $work13 - $warm
[int]$wset23   = $wset13 - $warm
[int]$wset33   = $wset23 - $warm

#Let's build the body
$body =  Write-Output `n "531 Workout!" `n
$body += Write-Output "Your estimated 1RM is: $weight" 
$body += Write-Output "Your $tmPretty training max is: $tm" 
$body += Write-Output "Warm up lbs: $warm" `n

$body += Write-Output "Week 1" `n

$body += Write-Output "Warm up sets:" `n

$body += Write-Output "$wset3 x 5"
$body += Write-Output "$wset2 x 5"
$body += Write-Output "$wset1 x 3" `n

$body += Write-Output "Work sets:" `n
$body += Write-Output "$work1 x 5"
$body += Write-Output "$work2 x 5"
$body += Write-Output "$work3 x 5+" `n
$body += Write-Output "BBB" 
$body += Write-Output "5x10 @ $bbbfwks" `n

$body += Write-Output "Week 2" `n

$body += Write-Output "Warm up sets:" `n
$body += Write-Output "$wset32 x 5"
$body += Write-Output "$wset22 x 5"
$body += Write-Output "$wset12 x 3" `n

$body += Write-Output "Work sets:" `n
$body += Write-Output "$work12 x 5"
$body += Write-Output "$work22 x 5"
$body += Write-Output "$work32 x 3+" `n
$body += Write-Output "BBB" 
$body += Write-Output "5x10 @ $bbbfwks" `n

$body += Write-Output "Week 3" `n

$body += Write-Output "Warm up sets:" `n
$body += Write-Output "$wset33 x 5"
$body += Write-Output "$wset23 x 5"
$body += Write-Output "$wset13 x 3" `n

$body += Write-Output "Work sets:" `n
$body += Write-Output "$work13 x 5"
$body += Write-Output "$work23 x 5"
$body += Write-Output "$work33 x 1+" `n
$body += Write-Output "BBB" 
$body += Write-Output "5x10 @ $bbbfwks2" `n `n
$body += Write-Output $emlSignature
$body = $body | Out-String

function Send-531Email {

    [cmdletbinding()]
    param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [string]
    $To,
    
    [string]
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    $Subject,
    
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    $Body)

    if (!$to)      {Write-Error "No recipient specified";break}
    if (!$subject) {Write-Error "No subject specified";break}
    if (!$body)    {Write-Error "No body specified";break}
   
       
    $SMTPClient  = New-Object Net.Mail.SmtpClient($SmtpServer, 587) 
    $SMTPMessage = New-Object System.Net.Mail.MailMessage($EmailFrom,$To,$Subject,$Body)

    $SMTPClient.EnableSsl = $true 
    $SMTPClient.Credentials = New-Object System.Net.NetworkCredential($emailUser,$emailPass); 
    
    $SMTPClient.Send($SMTPMessage)

}

if ($sendEmail) {Send-531Email -to $sendEmail -Subject "531 workout schedule for: $workoutName" -Body $body} else { $body }

PowerShell: Working with CSV Files

PowerShell: Working with CSV Files

Why did I want to do this?

I wanted to learn how to manipulate CSV files for logging purposes as well as for part of a script I was writing to email out a different quest each day. 

In particular I wanted to see if today was completed, and if not, update it to say so and then add the next day with Completed = No.

The setup

This function is part of a larger script that I'll write a post about after covering more of the basics.

You'll need the following things to get it running:

A file named runlog.csv in the same folder with the following contents:

“DateTime”,”Day”,”Completed”
”1/18/2016”,”1”,”No”

Preferably you'll want the first DateTime to match the current date, and day to start at 1. The script should be able to catch a gap in the DateTime and continue on, however.

Now you'll need to setup the following variables:

$todayDate        = (Get-Date).ToShortDateString()
$tomorrowDate     = (Get-Date).AddDays(1).ToShortDateString()
$runLog           = Import-CSV .\runlog.csv
$logHeaders       = @{
    "DateTime"  = '' 
    "Day"       = ''
    "Completed" = '' 
}

How to call the function

You'll want to call the function like so:

if ($updateLog)   {Log-DayQuest $todayDate $tomorrowDate $logHeaders}

At the beginning of the script you'd want something like:

[cmdletbinding()]
Param(
    [boolean]
    $updateLog
)

The code (let's put it all together!)

[cmdletbinding()]
Param(
    [boolean]
    $updateLog
)

$todayDate        = (Get-Date).ToShortDateString()
$tomorrowDate     = (Get-Date).AddDays(1).ToShortDateString()
$runLog           = Import-CSV .\runlog.csv
$logHeaders       = @{
    "DateTime"  = '' 
    "Day"       = ''
    "Completed" = '' 
} 

function Log-DayQuest {
    [cmdletbinding()]
    param($todayDate,$tomorrowDate,$logHeaders)
    
    [int]$questDay   = ($runLog | Where-Object {$_.DateTime -eq $todayDate} | Select-Object Day).Day
    
        if (($runLog | Where-Object {$_.DateTime -eq $todayDate} | Select-Object Completed).Completed -eq "Yes") {
    
            Write-Host "Log already updated!"
 
        } Elseif ($runLog | Where-Object {$_.DateTime -eq $todayDate})  { 

            [int]$day = ($runLog | Where-Object {$_.DateTime -eq $todayDate} | Select-Object Day).Day
        
            #Log today as completed
            ($runLog | Where-Object {$_.DateTime -eq $todayDate}).Completed = "Yes"        

            $runLog | Export-CSV .\runlog.csv -NoTypeInformation
          
            #Log tomorrow as not completed
            
            $logHeaders.DateTime  = $tomorrowDate
            $logheaders.Day       = $day+1
            $logheaders.Completed = "No"

            $newrow = New-Object PSObject -Property $logHeaders
            Export-CSV .\runLog.csv -InputObject $newrow -append -Force
            
            Write-Host "Log updated!"
        
        } elseif($runLog | Where-Object {$_.DateTime -eq $todayDate} -eq $null) {
            
            Write-Host "No entry for today... creating entry and updating"
            [int]$day = ($runlog[$runlog.count-1]).day 
            $logHeaders.DateTime  = $todayDate
            $logheaders.Day       = $day+1
            $logheaders.Completed = "Yes"
            
            $newrow = New-Object PSObject -Property $logHeaders
            Export-CSV .\runLog.csv -InputObject $newrow -append -Force

        }
}

if ($updateLog)   {Log-DayQuest $todayDate $tomorrowDate $logHeaders}

The results

Here are the results of me testing the script.

More on how it works coming up!

Please let me know what you think or if you have a quicker way to accomplish the same thing.

One of the things I love about PowerShell are the different ways to accomplish the same thing. That's the best way to learn. 

PowerShell: Working with the NetApp module (part 2) 7-Mode

PowerShell: Working with the NetApp module (part 2) 7-Mode

A couple weeks ago I shared part of a script that enables you to separate out the connection to 7-Mode and CDOT controllers.

If you missed that, check it out!

Today I will share the part that expands upon what you can do with the 7-Mode information.

The Code:

Function Get-7ModeInfo {


param ($naCredentials,
       $naController)
    
    if (Get-NASystemVersion) {
        $netAppSystemInfo = Get-NASystemInfo

        Write-Host `n"Controller information (7 Mode)" -ForegroundColor $foregroundColor

        Write-Host   "System Name      : " $netAppSystemInfo.SystemName 
        Write-Host   "System ID        : " $netAppSystemInfo.SystemId
        Write-Host   "System Revision  : " $netAppSystemInfo.SystemRevision 
        Write-Host   "System Serial    : " $NetAppSystemInfo.SystemSerialNumber
    
        Write-Host `n"Partner Information" -ForegroundColor $foregroundColor

        Write-Host   "Partner          : " $netAppSystemInfo.PartnerSystemName 
        Write-Host   "Partner System ID: " $netAppSystemInfo.PartnerSystemId

        Write-Host `n"Options..." -ForegroundColor $foregroundColor
        Write-Host "1. Reset your password"
        Write-Host "2. Search through volumes"
        Write-Host "3. Volume Space Report"
        Write-Host "4. Lun Report"`n
        Write-Host "'q' drops to shell"`n

        $selection = Read-Host "Which #?"
        
        Switch ($selection) {

            1 {
    
                $userName     = $NetAppCredential.UserName 
                $oldPass      = $netAppCredential.Password | ConvertFrom-SecureString        
                $newPassword  = Get-Credential -Message "Enter new password below" -UserName "Doesn't Matter"
                $newdPassword = $newPassword.GetNetworkCredential().Password

                Set-NaUserPassword -User $userName -OldPassword $oldPass -NewPassword $newdPassword
                
                Get-7ModeInfo

            }

            2 {

            }

            3{ 

            Get-NaVol | Select @{Name="VolumeName";Expression={$_.name}},@{Name="TotalSize(GB)";Expression={[math]::Round([decimal]$_.SizeTotal/1gb,2)}},@{Name="AvailableSize(GB)";Expression={[math]::Round([decimal]$_.SizeAvailable/1gb,2)}},@{Name="UsedSize(GB)";Expression={[math]::Round([decimal]$_.SizeUsed/1gb,2)}},@{Name="SnapshotBlocksReserved(GB)";Expression={[math]::Round([decimal]$_.SnapshotBlocksReserved/1gb,2)}},SnapshotPercentReserved,Aggregate,IsDedupeEnabled,type,DiskCount,RaidStatus,Autosize,ChecksumStyle,state | Export-Csv -LiteralPath $Location\$nacontroller.csv -Force -NoTypeInformation -Verbose
            Start-Process .\$nacontroller.csv
            
            Get-7ModeInfo
            
            }

            4{

            Get-NaLun | Select Path,@{Name="TotalSize(GB)";Expression={[math]::Round([decimal]$_.TotalSize/1gb,2)}},@{Name="UsedSize(GB)";Expression={[math]::Round([decimal]$_.SizeUsed/1gb,2)}},Protocol,Online,Thin,Comment | Export-Csv -LiteralPath $Location\$nacontroller"luns".csv -Force -NoTypeInformation -Verbose
            Start-Process .\$nacontroller"luns".csv
            
            Get-7ModeInfo

            }
             
            q {Write-Host "I see 'q', dropping to shell..." -foregroundcolor $foregroundcolor;break}

            }
        }
}

This is very basic and some of the switch options are not even set up yet. I used it as a shell to get some ideas for further administration. Coming up next will will be the same thing but with CDOT commands!

As always, let me know if you have any ideas or comments.