When the Question Is Not So Simple; What’s Your Version of Dot Net?


Surprisingly, trying to determine the level of .Net (dotNet) installed on a system is a difficult thing to do. dotNet does not self-report the version in use. Since dotNet is a collection of items plus the CLR (Common Runtime Language), file checking is convoluted and often inaccurate. Even the version of record – contained in the Registry, is fragmented due to the evolving nature of its programming (I’m not a registry expert but this is one of only a few times where there is no definitive key=value pair. What makes it worse is the method of recording the versions in the registry has changed over the years).  There is simply no easy or pre-built solution to checking the versions of .Net installed. Which means, there cannot be a simple Powershell script that will check for the installed versions of .Net on any specific system. Legacy can have its issues.

However, most of the time, we don’t really care about any previous installation. We just need to know what is the current version (usually for compatibility reasons). Today’s posting is about a simple (sort of) script that will answer that for most business machines.  We will do this by using an assumption script. Assumption scripts ‘assume’ certain factors exist in the operating environment. In this case, a certain level of .Net is installed; specifically dotNet version 4.5 or higher. For Windows 7 machines and above, this is a pretty safe assumption. Without becoming a primer on the differences, let’s just say the installations prior to .net 4.5 is where the registry structure issues reside.

The root path in the registry for dotNet is in the HKEY_Local_Machine directory: HKLM\SOFTWARE\Microsoft\.NetFramework…

When talking about dotNet 4.5, the registry keys reside under the cryptic subdirectory v4.0.30319 and are listed by SKUs. However, this is where things get interesting because instead of a key value pair or further sub-directories, you find a ‘product listing’. Looking like a subdirectory, the name will be a mashup of the term ‘.NETFramework’ and a key=value pair looking ‘Version=vXXX’ pattern (the mashup is done with a comma symbol ‘,’). The content of these, as such, is an unset Default value.  Prior to version 4.5, there is a second sub-directory that extends the mashup with a Profile=Client addition that adds even more confusion and no actual information. Fun stuff.

So, bearing the above in mind, a Powershell script that returns the highest level of dotNet installed will have to find these sub-directories, split out the mashups, and then apply logic to determine the highest level. Easy peasy right!?!

$S = gci “HKLM:Software\Microsoft\.NetFramework\v4.0.30319\SKUs” | sort pschildname
$A = $S[($S.length)-3].ToString()

Line 1: Get all the SKUs entries from the registry path. Sorting them ascending will give us a known structure

Line 2: There are two entries under SKUs that are non-standard (of course). So, we are going to find the third entry which will be our senior .Net installation

Line 3: Break the mash-up apart and return the salient part (the second part that shows the version number).

I borrowed the above code from a function I wrote (with all the try/catch, write-debug, validates, remote computer options, functions can give you). However, when I was first using this, it was a one-liner that used the semicolon token “;” to emulate the separate lines. If that doesn’t make sense to you, here it is as a one liner:

$S = gci “HKLM:Software\Microsoft\.NetFramework\v4.0.30319\SKUs” | sort pschildname ; $A = $S[($S.length)-3].ToString() ; ($A.split(“{=}”))[1]}

*** With a small amount of rewrite, this script could give you all versions (even prior to v4.5) if you so desired. ***

[NOTE: There is also an entry that is more key=value like. HKLM\SOFTWARE\Microsoft\Net Framework Setup\NDP\V4\Client. You will have to translate the Release= DWORD entry and know whether the machine being tested is Windows 10 or not. And, if it is Windows 10, you’ll need to know which flavor (Nov. Update, Anniversary Update, or Creators Update). However, they MAY be a Version key. If there IS a Version key, it does work exactly as you would expect a registry structure to work. B-T-W the DWORD entry for Version 4.7 is 0x000707fe (460798) on a Windows 10 Creator Update edition.]


Tags: .Net, dotNet, .Net Version, Registry, Powershell


### All Things Array ###


The below is a Powershell script used in ISE for training…

#Start by creating an Array. Use Get-Random to save some typing
[array]$array = Get-Random -Count 10 -InputObject(1..1000)

#But we need to know the array contents for a tutorial
[array]$array = @(179,904,469,524,503,549,524,808,328,524)

#Show the new array using the standard method (which is very VB looking).
For($i=0;$i-le $array.Length-1;$i++) {
“`$array[{0}] = {1}” -f $i, $array[$i]}

#Create a function to provide a better formatted output
function Show-ArrayIndex {$counter = 0; $input | Out-String -Stream | % { (“[{0:0000}] ” -f $counter++) + $_.TrimEnd() }} #Much more -Like Powershell
$array | Show-ArrayIndex

#Let’s do strings too
$ArrayStrings = @(“JimBob”,”Jeff”,”Daner”,”Deanna”,”Jacob”,”Heidi”,”AlWright”,”User1″,”User2″,”User3″)
$ArrayStrings | Show-ArrayIndex

### Working With Arrays ###

#Splitting the array
#You can use either Count or Length to determine the number of items in the array.
‘$array.Count is ‘ + $array.Count
‘$array.Length is ‘ + $array.Length

#Because arrays start at 0, even number lengths must be rounded to divide properly
“`n ” + $array.GetUpperBound(0) + ” is not the same as ” + $array.Count + “`n UpperBound() divided by 2 will therefor give ” + ($array.GetUpperBound(0)/2)

#Getting the Lower Half – use [int] to round off the number
“The lower half (first 5) is ” + $array[0..([int]($array.GetUpperBound(0)/2))]

#Getting the Upper Half – Add .5 to even things up with an odd number (an old VB trick)
“The Upper half (last 5) is ” + $array[(($array.GetUpperBound(0)/2)+ .5)..($array.GetUpperBound(0))]

#Adding a member to an array is easy
‘$Array.Count is ‘ + $array.Count #show original array size
$array += 99
“`$Array.Count is now $($Array.Count)” #this size should be one larger now
$array | Show-ArrayIndex

#Removing array items is not so easy
#Discussion – Issue here is ArrayType[] is a fixed size. In fact, doing ‘+=’ actually creates a new array (witht the same name as the old array).
# But an ArrayListType[] is of indeterminate size (should say that it is Schrödinger length)
# Fix then is to re-cast the Array[] to Arraylist[] and use the Remove method of the object.

[Collections.ArrayList]$array = $array


“`$Array.Count is now $($Array.Count)”
$array | Show-ArrayIndex

#reset the Type[]
[array]$array = $array

#Powershell does not have a method to find the array position of an item. Use the static method of .Net to do this
# First, identify the item you want to look for
$ItemOfInterest = 524

#Then use the discussed .Net method

#What if there are two numbers that are the same? We can find the last one using the same static function System.array but method LastIndexOf

#Worse yet, what if we don’t know how many duplicates there are?
# Do a Where-Object search (where-object is a looping function). Note that the part before the Pipe symbol is to reduce the size of the search (what if there were 100K records?).

(0..([array]::LastIndexOf($array,$ItemOfInterest))) | where {$array[$_] -eq $ItemOfInterest}

#Since an ArrayType[] is of System.object, Sort and Select will work.
$array | Sort-Object -Unique
$array | Select-Object -Unique

#tags: Array, ArrayList, LastIndexOf, Powershell


Removing -Persist PSDrives

To create a mapped network drive in Powershell, you use:
New-PSDrive -Name “S” -Root “\\Server01\Scripts” -Persist -PSProvider “FileSystem”

Although this is meant for remote servers, I tried referring the local machine as a server and it worked.

New-PSDrive -Name “S” -Root “\\localhost\Scripts” -Persist -PSProvider “FileSystem”

The only drawback was there’s no entry in File Explorer (you shouldn’t be using that anyway). Additionally, a problem was found trying to remove this new drive:

Get-PSDrive -Name S | Remove-PSDrive -force
Nope, drive still shows in Get-PSDrive

Remove-PSDrive -Name S -PSProvider FileSystem -Force
Nope, still there.


Net Use S: /del (old school command)
This worked!

Funny thing though. Retrying this exercise a second time created the drive as before but now Get-PSDrive S | Remove-PSDrive -force worked just fine.

Live and learn I guess…

tags: Mapped Drive, drive, drives, mapping drives, Remove-PSDrive, Get-PSDrive, Powershell

May 2017


Articles written in May 2017

1. ErrorAction in Action
2. Quick Hitters
3. Difference: Console, Jobs, or Workflow
4. Working with PSSessions
5. Remoting Gotchas
6. Snippets
7. How to Publish (Your Errors)
8. Casting with -AS and Evaluating with $()
9. Unblocking Files
10. Updating Help In a Timely Manner

tags: May, Volume 1, Menu, Table of Contents, Article list 1, Powershell

Updating Help In a Timely Manner


The command to update help (yes, help files should be updated) is Update-Help. Pretty simple right? The problem comes in remembering to do this. I had it in my $Profile script but that really slowed down starting Powershell. And some days I may open/close/open Powershell numerous times. Running Help update each time was way too crazy. But without something automatic, I tend to forget about doing this task until it comes and bites me in the …

So last night I decided to write a quick script that would
1. Get the date of the last time help was updated from a file.
2. If the current date was 7 days or more since that date, run Update-Help.
3. Overwrite the date in the file with the this new update date (for next time).

Sounds like an easy function correct? Well, as they say, “the devil is in the details.” Or in this case, the devil is in casting data types!

Data types is a way of flagging to the computer what kind of information it is looking at. The most common are [string] – meaning characters, words, and such, and [int] which is short for integer – meaning numbers. Others include [bool] (Boolean – yes/no type results), [decimal] (fractional numbers), and [datetime]. For a fuller list, see TechNet – Powershell Types.

However, the usual lists given for Powershell types does not include Object. And that is a real problem. In this case, storing a date in a file was fine until I tried to retrieve it. Get-Content returns an OBJECT. That is by design as the program does not know what it is ever going to return so it encapsulates the data in an array and wraps that in an object container (as best as I can tell).

Problem here is while Powershell has a variety of ways to re-cast data types, [datetime] would not work for an object. Even extracting the array data (Foreach) as type [string] still failed. Most frustrating was using the built-in method for [string].ToDatetime() did not work (nor did temp casting with the  -as keyword). What finally worked was to re-cast using multiple variables, ending up with this:

Function Update-HelpWeekly {
Begin   {
$LastChecked = Get-Content -Path C:\UpdateDateHelp.txt

Process {
$LastCheck = [datetime]$LastChecked
$evenDays = ((Get-Date).AddDays(-7)).ToShortDateString()

If($LastCheck -le $evenDays) {
Update-Help -Force

Get-Date -Format d | Out-File C:\UpdateDateHelp.txt


Yep, this script should not work but it has been tested on a few machines successfully. A variable holds the Get-Content object and then gets assigned to another variable as a different data type. There is no breaking out the array or splitting of data. Specifically $LastCheck is assigned $LastChecked as a datetime data type even though $LastChecked cannot be directly re-cast as a datetime object! Bizarre. ($LastChecked.ToDateTime(), [datetime]$LastChecked, and $LastChecked -as [datetime] all failed in the script, even when packed inside parenthesizes or evaluated $() ).

As hard as this ending up being (2 hours of *.GetType()) it was worth every drop of sweat because of all the learning and affirming with data types. This ended up being worth so much more than just creating a convenience function.

tags: Data type, Data Types, gettype(), datetime, casting, recasting, re-casting, Powershell


Unblocking Files

Are you all blocked up? Maybe downloaded a zip file or a directory and now you need to unblock the file (’cause you have no security concerns about the content).
Well, it’s complicated, but there is a way to do that in Powershell.
Here is the command:

Unblock-File -Path (-verbose)

(A path that stops at a directory level (folder) will unblock all the files inside.)

That’s it and that’s that. I like to use the option –verbose in this case, just to get the feedback because this command is quick and does not generate any comments on its own.

Here are the rest of the members in the Unblock Family:

Unblock-FileShareAccess -Name -AccountName -FileServer
Unblock-SmbShareAccess -Name -AccountName -Force
Unblock-Tpm -OwnerAuthorization

#tags: file security, folder security, locked files, Powershell, Unblock

Casting with -As and Evaluating with $( )


$a = “12” ; $b = “4”
$a.GetType().Name  #String
$b.GetType().Name  #String

$c = $a + $b
$c                                  #124 (results are concatenated)
#Without casting, Powershell determines type from content.
$c.GetType().Name   #Int32

$c -as [string]              #124
[string]$c                     #124
$c.GetType().Name    #Still Int32

#Instance casting of the content
$c =  $($a -As [int]) / $($b -As [int])

$c.GetType().Name  #Still Type Int32
$b.GetType().Name  #Still Type String
$a.GetType().Name  #Still Type String

#Re-casting variables contents
$c = [int]$a / [int]$b

$c.GetType().Name   #Still Type Int32
$b.GetType().Name   #Still Type String
$a.GetType().Name   #Still Type String

#Casting the variable
PS C:\> [int]$c = $a / $b
PS C:\> $c

$c.GetType().Name   #Still Type Int32
$b.GetType().Name   #Still Type String
$a.GetType().Name   #Still Type String

tags: Casting, Casting on the fly, Copy & Paste, Powershell, $S, type, type conversion


How to Publish (Your Errors)


Sorry about the lapse in blogging. My workmates attended a 3-day conference and left me, like the step-daughter in fairy tales, hard at work.  Then the weekend came along and spring finally arrived in Wisconsin (I have three city lots of yard work). My final excuse is spending the first two days of this week at the CCCP Expo in Green Bay. A big thank you to them for putting on such a great event and even providing suites for us at the wonderful Hyatt.

Now on to business. If you look back in this blog log, you will see an earlier posting about error handling. https://poshlessons.wordpress.com/2017/05/09/erroraction-in-action/. This post is a follow up to that (or maybe the conclusion it should have had). It is a discussion on what to do once you have captured errors in your scripts.

I am not going to talk about formatting data for readability and then writing them to some log file. Just use OUT-FILE for that. But frankly, the last thing we need is more folders to search when troubleshooting issues. Instead of that, we are going to use big boy reporting – sticking the information into the Event Log. It makes sense when you think about it. We go to the event logs all the time to look for messages that may help us understand what is going on with O/S issues. It is the logical place then to put our performance output as well and keep troubleshooting focused in one area.

The basic command to do this is Write-EventLogLognameSourceEventIdMessage. These parameters are mandatory and if you don’t supply them, the program will ask. -EntryType and possibly -Category are other parameters you may want to include.

There are some quirks in a few of these parameters; –Source has to be a specific term. You cannot specify some random name you just made up for your entries (or can you?). Sources are registered with the operating system. If you are going to use one of them, you will need to find which ones are available.

Just kidding. The command New-EventLog will not only create a new event log but it can also register sources of your own creation. Since we are going to be introducing something new that was unplanned by the original programmers, we will be creating custom sources for Powershell scripts in this blog.

The other non-intuitive parameter is Category. You’d think it would be of type string but it is actually of type [INT16] (a number). What is supposed to happen behind the scenes is that a category dictionary is created that will translate the numbers into string equivalent messages. For instance, the number 100 will mean Routine, etc. If you want to do this, remember that the path to this file will need to be set in the New-EventLog command (use –CategoryResourceFile parameter). However, I am good with just using a number with no translation needed.

Enough background, let’s get started. Since we are capturing Powershell data, it seems logical to store our output in the existing log ‘Windows Powershell’. You can find it in the Event Viewer (eventvwr.exe) under Applications and Service Logs > Windows Powershell however because it is a foundational log, we can just use its name (Windows Powershell) for addressing [To address other logs, you’ll have to provide their path]. To see all the directly accessible logs, use Get-EventLogList.

While we are using an existing log, we are planning on using a couple of new sources. One will be for script errors and the other for routine messages. (Routine messages can be important so that you are able to document the value of the product and can provide tracking data on when and how much it is being used).

Note that creating new sources is a one-time function so you’ll need to plan where and when to run the command and how to silently continue if the sources already exist. If I haven’t mentioned it before, unless you are using a single server to capture all events in your domain(s), you will need to create these custom sources on each (and every) machine. These are the commands:

New-EventLog -LogName ‘Windows Powershell’ -Source ‘ScriptErrors’
New-EventLog -LogName ‘Windows Powershell’ -Source ‘Scripts’

You can now use ScriptErrors or Scripts as sources.

The other parameters: Unlike sources, you can use any number you want between 0 and 65535 for an EventId (because EventIds are application specific). I know of some programmers that generate random numbers for each entry but the primary use of EventIds is for aggregating/searching so the recommendation is to pick specific numbers to represent specific classes of information. For instance, all informational messages about a script starting/stopping normally get ID 1. Warning messages often use id 999 generically (that’s an old programmer’s trick) or have a list of numbers based on specific exception messages. Use whatever you want, just be consistent.

You should also provide the Entry Type. There are five of them; Information, Warning, Error, Success Audit, and Failure Audit. Warning is a good label for non-terminating errors, saving Error for those scripts that completely bomb-out. Event type Information should be used for messages pertaining to normal script operations. This is where you will capture and save all that ‘justification’ data we talked about earlier.

With all the pieces gathered we can now run our commands ($Lm is the variable I use to hold routine log messages. $e is for errors.):
Write-EventLog -Logname ‘Windows Powershell’ -Source Scripts -EntryType Information -EventId 0 -Message $Lm
Write-EventLog –Logname ‘Windows Powershell’ -Source ScriptErrorsEntryType WarningEventId 999Message $e

Populate your message variables and run the command. Refresh the Event Viewer interface if you have it open and you will now see a new entry in the Windows Powershell event log. Pretty cool, right?!

To finish this up, there will have to be a blog about retrieving information from EventLogs (Get-EventLog maybe?)

#tags: Powershell, Event log, Event Logs, Write-EventLog, Error handling, Errors, New-EventLog


Snippets – Essential Powershell Skill


Functions are great productivity helpers for frequently used or critical tasks. But for those things that are done only occasionally, it becomes hard to remember that you have a function for that, what the exact command is, what the parameters are, and what values are needed. To address this I’d like to introduce you to the SNIPPET.

Snippets are a feature of Powershell ISE. Powershell ISE is the tool you should be using for administration and it is my recommendation that you always have it up and running during work (time to retire POPS).

A snippet is just like a template (sometimes called a boiler plate). It can contain any sort of text. It can be used for scripts, functions, notes, reminders; generally whatever you’d like to be able to paste into a document. You open the snippet dialog box with CTRL + J (or in the toolbar EDIT > Start Snippets). The collection of snippets is displayed as a list. When you mouse over an item of interest, a window pops up with the text of the snippet. You double-click (or hit enter on the keyboard) to insert the text (your cursor should be at the spot where you want the insert to go). This can be either in the script pane (the white background area) or the command console itself.
Initially when you open the snippet dialog box there will be a list of default snippets. This can be turned off in the toolbar: Tools > Options: General Settings tab – Other Settings/Use default snippets (uncheck). Unfortunately, the option is only to turn them off or on (you cannot select individual ones). I’d leave them on to start with. However, what is important is that you can create your own and add them to the list! There is even a Powershell command to do this (actually it is the only way to do it):

New-IseSnippet [-Title] <String> [-Description] <String> [-Text] <String> [-Author <String>] [-CaretOffset <Int32>] [-Force]

Here is how it works:
–Title (How you want it labeled)
–Description (your description. It shows at the very top so it is best if this is short)
–Text (This is where you are going to put your commands and parameters).

These are the required parts of the command meaning that if you just type New-IseSnippet and hit enter, it will ask you for those three items. The optional parameters are: –Author (author name) and –CaretOffset. CaretOffset allows you to specify where you want the cursor to be after pasting in the command (like right at the part where you put in the first parameter…)

Want a snippet for resetting AD account passwords?

You run this command in your ISE one-time and the command will be created and added to your snippet list, ready to help the next time someone has forgotten their password. When they call, hit Ctrl+J in the ISE window, find “Reset Password” entry, and double-click it (or hit the enter key). The command string will be inserted with the cursor at the place where the logon name is inputted. (In this case, either username or FQDN will work).

Once you create a snippet, it is stored as a XML type file (<SnippetTitle>.ps1xml) in your profile (documents/WindowsPowershell/Snippets) directory. This means it is only available for you. But you can share this file with anyone. They would just put a copy in their profile directory location. This is also how you remove any practice or unwanted snippets (delete/remove them from this folder).

Having one-liners available this quick is really essential to being efficient with Powershell. Practice this one until you are comfortable making your own snippets. Like using Get-Help or Get-Command, this is an essential skill in using Powershell.

tags: Powershell, Snippets, Productivity, Oneliners, Powershell ISE, ISE


Remoting Gotchas


If you are having issues with remoting in Powershell, here are a few of the most often overlooked culprits:
1. You can only remote from a console if it is in an elevated state (open PW > run as administrator)
2. Remoting is only by NetBios Name. They do not support IP addresses as I write this.
3. Enable-Remoting does not configure third party software. Only the Windows firewall on the local machine.
4. Remember, remoting is an inbound configuration. Outbound is not restricted by Powershell.
5. The ports for WinRM/WS-Man are 5985-5986. Only listeners, and only on those ports, are created.
6. And finally, remoting is an Active-Directory thing. You cannot remote to non-domain computers (no trust relationship).

#tags: Powershell, Remoting, Enable-PSRemoting, WinRM, WinRM quickconfig