Sending Files to Your Browser with PowerShell

Over the course of the last year I’ve been using markdown files much more, especially as part of the Platyps module. Even though I have a markdown editor and I can also preview files in VS Code, sometimes I want to see the file in my browser which has a markdown viewer plugin. Or I might want to see something else in my browser. I had been pasting the file path and pasting it into the browser, but of course realized I should get smart about this and write a PowerShell function to make this easier. Thus was born Out-Browser. Although it proved a bit trickier than I expected because the registry was involved.

Continue reading

Scraping Sysinternals

download-thumbRecently I was conversing with someone about my PowerShell code that downloads tools from the live Sysinternals site. If you search the Internet, you’ll find plenty of ways to achieve the same goal. But we were running into a problem where PowerShell was failing to get information from the site. From my testing and research I’m guessing there was a timing issue when the site is too busy. So I started playing around with some alternatives.

I knew that I could easily get the html content from http://live.sysinternals.com through a few different commands. I chose to use Invoke-RestMethod for the sake of simplicity.

Because $html is one long string with predictable patterns, I realized I could use my script to convert text to objects using named regular expression patterns. In my test script, I dot source this script.

Now for the fun part. I had to build a regular expression pattern. Eventually I arrived at this:

If you have used the Sysinternals site, you’ll know there is also a Tools “subfolder” that appears to be essentially the same as the top level site. My pattern is for the top level site. Armed with this pattern, it wasn’t difficult to create an array of objects for each tool.

sysinternals

Next, I check my local directory and get the most recent file.

Then I can test for files that don’t exist locally or are newer on the site.

If there are needed files, then I create a System.Net.WebClient object and download the files.

The end result is that I can update my local Sysinternals folder very quickly and not worry about timing problems using the Webclient service.

Convert Text to Object with PowerShell and Regular Expressions

squarepatternA few weeks ago I was getting more familiar with named captures in regular expressions. With a named capture, you can give your matches meaningful names which makes it easier to access specific captures. The capture is done by prefixing your regular expression pattern with a name.

When you know the name, you can get the value from $matches.

This also works, and even a bit better, using a REGEX object.

With the REGEX object you can get the names.

Because the names include index numbers, I usually filter them out. Once I know the names, I can use them to extract the relevant matches.

Then I realized it wouldn’t take much to take this to the next step in PowerShell. I have a name and a value, why not create an object? It isn’t too difficult to create a hashtable on the fly and use that to create a custom object. Eventually I came up with ConvertFrom-Text.

The function requires a regular expression pattern that uses named captures. With the pattern you can either specify the path to a log file, or you can pipe structured text to the function. By “structured text” I mean something like a log file with a predictable pattern. Or even output from a command line tool that has a consistent layout. The important part is that you can come up with a regular expression pattern to analyze the data. I also wanted to be able to pipe in text in the event I only wanted to process part of a large log file.

Here’s an example using the ARP command.

In this particular example, I’m trimming the ARP output to remove any leading or trailing spaces from each line and then converting each line to an object, using the regular expression pattern.

convertfrom-text

If you haven’t jumped to why command is useful, is that once I have objects I can easily filter, sort, group, export, or just about anything else. By converting a log file into a collection of objects I can do tasks like this:

convertfrom-text-2

I hope some of you will try this out and let me know what you think. What works? What is missing? What problem did this solve? Inquiring minds, well at least mine, want to know. Enjoy.

Friday Fun: Expand Environmental Variables in PowerShell Strings

This week I was working on a project that involved using the %PATH% environmental variable. The challenge was that I have some entries that look like this: %SystemRoot%\system32\WindowsPowerShell\v1.0\. When I try to use that path in PowerShell, it complains because it doesn’t expand %SystemRoot%. What I needed was a way to replace it with the actual value, which I can find in the ENV: PSdrive, or reference as $env:systemroot. This seems reasonable enough. Take a string, use a regular expression to find the environmental variable, find the variable in ENV:, do a replacement, write the revised string back to the pipeline. So here I have Resolve-EnvVariable.

Function Resolve-EnvVariable {

[cmdletbinding()]
Param(
[Parameter(Position=0,ValueFromPipeline=$True,Mandatory=$True,
HelpMessage="Enter a string that contains an environmental variable like %WINDIR%")]
[ValidateNotNullOrEmpty()]
[string]$String
)

Begin {
Write-Verbose "Starting $($myinvocation.mycommand)"
} #Begin

Process {
#if string contains a % then process it
if ($string -match "%\S+%") {
Write-Verbose "Resolving environmental variables in $String"
#split string into an array of values
$values=$string.split("%") | Where {$_}
foreach ($text in $values) {
#find the corresponding value in ENV:
Write-Verbose "Looking for $text"
[string]$replace=(Get-Item env:$text -erroraction "SilentlyContinue").Value
if ($replace) {
#if found append it to the new string
Write-Verbose "Found $replace"
$newstring+=$replace
}
else {
#otherwise append the original text
$newstring+=$text
}

} #foreach value

Write-Verbose "Writing revised string to the pipeline"
#write the string back to the pipeline
Write-Output $NewString
} #if
else {
#skip the string and write it back to the pipeline
Write-Output $String
}
} #Process

End {
Write-Verbose "Ending $($myinvocation.mycommand)"
} #End
} #end Resolve-EnvVariable

The function takes a string as a parameter, or you can pipe into the function. The function looks to see if there is something that might be an environmental variable using a regular expression match.


if ($string -match "%\S+%") {

If there is an extra % character in the string, this won’t work so I’m assuming you have some control over what you provide as input. Now I need to get the match value. At first I tried using the Regex object. But when faced with a string like this “I am %username% and working on %computername%” it also tried to turn % and working on% as an environmental variable. I’m sure there’s a regex pattern that will work but I found it just as easy to split the string on the % character and trim off the extra space.


$values=$string.split("%") | Where {$_}

Now, I can go through each value and see if there is a corresponding environmental variable.


foreach ($text in $values) {
#find the corresponding value in ENV:
Write-Verbose "Looking for $text"
[string]$replace=(Get-Item env:$text -erroraction "SilentlyContinue").Value

I turned off the error pipeline to suppress errors about unfound entries. If something was found then I do a simple replace, otherwise, I re-use the original text.


if ($replace) {
#if found append it to the new string
Write-Verbose "Found $replace"
$newstring+=$replace
}
else {
#otherwise append the original text
$newstring+=$text
}

In essence I am building a new string adding the replacement values or original text. When finished I can write the new string, which has the variable replacements back to the pipeline.


Write-Verbose "Writing revised string to the pipeline"
#write the string back to the pipeline
Write-Output $NewString

Finally, I can pass strings that contain environmental variables to the function.


PS C:\> "I am %username% and working on %computername%" | resolve-envvariable
I am Jeff and working on SERENITY

This isn’t perfect. Look what happens if there is an undefined variable:


PS C:\> "I am %username% and working on %computername% with a %bogus% variable." | resolve-envvariable
I am Jeff and working on SERENITY with a bogus variable.

But as long as you are confident that variables are defined, then you can do things like this:


PS C:\> $env:path.split(";") | Resolve-EnvVariable | foreach { if (-Not (Test-Path $_)) {$_}}
c:\foo

Download Resolve-EnvVariable and let me know what you think. The download version includes comment based help.

Get IP Addresses with PowerShell

In celebration of World IPv6 Day, I thought I’d post a little PowerShell code to return IP addresses for a computer. This information is stored in WMI with the Win32_NetworkAdapterConfiguration class. This class will return information about a number of virtual adapters as well so I find it easier to filter on the IPEnabled property.

The returned object has an IPAddress property which is an array of addresses. In here you will find IPv4 and IPv6 addresses. My quick and dirty script uses a relatively simple regular expression pattern to get those addresses.


Param([string]$computername=$env:computername)

[regex]$ip4="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"

get-wmiobject win32_networkadapterconfiguration -filter "IPEnabled='True'" -computer $computername |
Select DNSHostname,Index,Description,@{Name="IPv4";Expression={ $_.IPAddress -match $ip4}},
@{Name="IPv6";Expression={ $_.IPAddress -notmatch $ip4}},MACAddress

In the Select-Object expression I’m defining custom properties to get the different IP addresses. Here’s what it looks like in action.


PS E:\> C:\scripts\Get-IP.ps1 -comp quark

DNSHostname : Quark
Index : 10
Description : Realtek PCIe FE Family Controller
IPv4 : 172.16.10.126
IPv6 : fe80::ddcf:c714:44c8:bcc3
MACAddress : C8:0A:A9:04:C5:32

If you had a number of computers you could pipe the results to Where-Object and get just the IPv6 machines (or not).


... | where {$_.Ipv6} | Sort DNSHostname | Select DNSHostname,Description,IPv6

Feel free to download Get-IP and try it out for yourself.