PowerShell Show and Tell Tomorrow Night

Tomorrow night is PowerShell Show and Tell.

Got a cool project to share? Stop by and tell us about it.

Got questions about PowerShell? Stop by and get answers.

Share what you're working on, or what you wish you could work on.

Party starts @ 6:00 PM Pacific Time.

If you have something you want to ask or share, shout out in the comments.

PowerShell Show and Tell

If you're looking for more PowerShell events, two more are coming up:

I hope to see you there, and I'd love to see what you have to share.

reddit.com
u/StartAutomating — 3 days ago

Simple Shortcuts

This week there was a thread about the best way to create a shortcut to a script.

I made a quick open-source module for shortcuts called Shortcut.

Then I typed up a long read about how to create shortcuts on Windows and Linux. Then it got flagged.

Once more, with feeling (with fewer links and a syntax trick)

Creating Shortcuts on Windows

Windows Shortcuts can be created thru the Windows Script Host's .CreateShortcut method

We can create a Windows Script Host shell object with New-Object -ComObject

Since shortcuts can be malicious, some services flag examples of using this directly, so we're going to have to construct our object in a bit of a funky way.

$wsh = New-Object -ComObject ('WScript','Shell' -join '.')
$wsh.CreateShortcut("./pwsh.lnk")
$wsh.WindowStyle = 3
$wsh.TargetPath = 'pwsh'
$wsh.Save()

We can also make shortcuts to a .url file about the same way. For url files, we can only provide a target path.

$wsh = New-Object -ComObject ('WScript','Shell' -join '.')
$wsh.CreateShortcut("./some.url")
$wsh.TargetPath = $url
$wsh.Save()

Creating Shortcuts on Linux

Linux shortcuts are .desktop files. Linux being Linux, of course this is a completely different format. It's just a simple key-value pair, like so:

[DesktopEntry]
Type=Application
Exec=/usr/bin/pwsh
Terminal=true

We also have to chmod +x any desktop entry, so it can run. And, at least on Kali Linux, we have to also use gio set ./some.desktop metadata::trusted true to say we trust the shortcut.

Creating Shortcuts with Shortcut

Shortcut gives us a simple script to create shortcuts.

Here's how those examples look when we take a Shortcut.

# Fullscreen powershell shortcut
shortcut "./pwsh.lnk" -TargetPath pwsh -FullScreen 

# Shortcut to url 
shortcut "./some.url" -Url $url

# Desktop file
shortcut "./pwsh.desktop" -DesktopEntry ([Ordered]@{
     Type='Application'
     Exec='/usr/bin/pwsh'
     Terminal='true'
})

Long Ways and Short Cuts

I think it is important people know how to do things without the tools. The tools are just a shortcut (in this case, quite literally).

The long way to making shortcuts on Windows is using the Windows Script Host's .CreateShortcut method.

The long way to making shortcuts on Linux is creating a .desktop file.

If you want a shortcut to shortcuts, this mini module will probably help you out.

u/StartAutomating — 13 days ago

Does Script Sharing Ban Links Now?

Update

Links are fine. It's mentioning WScript dot Shell that gets a post insta-flagged.

🤦🤦🤦


I've had a bit of a frustrating day.

A thread this week talked about the difficulties of creating shortcuts to scripts.

So I made an open-source module to create shortcuts.

Spent about an hour writing out a nice long read on how to create shortcuts.

Got insta flagged

Spent another half hour rewriting it to something shorter and taking out some links 🤞

Got insta flagged

Took it down to a couple of short paragraphs.

Still insta flagged.

Does Script Sharing auto flag all links now?

How are we supposed to share scripts if we can't actually share scripts?

Apologies for the frustration. Any insight into what causes a post to be auto flagged would be great. These posts only linked to GitHub.

I'm just trying to share scripts and knowledge. In this case, I was also attempting to answer a community question in a useful and durable way.

Any guidance is appreciated.

reddit.com
u/StartAutomating — 13 days ago

Zippy - A Quick Compression Module

Compression can be quick and easy with .NET.

Let's learn how.

Yesterday I just dusted off some old code and added some new tricks.

Today I dropped a quick compression module called Zippy

Let's see it in action and learn how it works.

Zippy Examples

# Compress a string using Brotli, output in base64
Compress-Zippy "Hello World"

Compress-Zippy "Hello Brotli" -Algorithm Brotli |
    Expand-Zippy -Algorithm Brotli

Compress-Zippy "Hello Deflate" -Algorithm Deflate |
    Expand-Zippy -Algorithm Deflate

Compress-Zippy "Hello GZip" -Algorithm GZip |
    Expand-Zippy -Algorithm GZip

Compress-Zippy "Hello ZLib" -Algorithm ZLib |
    Expand-Zippy -Algorithm ZLib

Compression in PowerShell

PowerShell is built on .NET, and .NET happens to have built-in support for four compression algorithms: Brotli, Deflate, GZip, and Zlib. We can compress data with any of these algorithms by using classes in the System.IO.Compression namespace, for example:

# Create a message
$message = "hello world"
# Get it as bytes
$bytes = $outputEncoding.GetBytes($message)
# Create a memory stream
$memoryStream = [IO.MemoryStream]::new()
# Create a compressor using the stream
$compressor = [IO.Compression.BrotliStream]::new(
     $memoryStream, [IO.Compression.CompressionLevel]::Fastest
)
# Write our bytes to the compressor
$compressor.Write($bytes,0, $bytes.Length)
# Close our compressor
$compressor.Close()
$compressor.Dispose()
# Get our compressed bytes
$compressedBytes = $memoryStream.ToArray()
# and output them
$compressedBytes

Decompression in PowerShell

Now let's go the other way around. It's easier.

# Create a new memory stream, containing our compressed bytes
$memoryStream = [IO.MemoryStream]::new($compressedBytes)
# Create a decompressed stream
$decompressedStream = [IO.Compression.BroitliStream]::new(
    $memoryStream, [IO.Compression.CompressionMode]::Decompress
)
# Create our output stream
$outputStream = [IO.MemoryStream]::new()
# Copy our decompressed stream to it
$decompressedStream.CopyTo($outputStream)
# Seek to the start (it outputs a position so null that out)
$null = $outputStream.Seek(0,'begin')
# Make a stream reader 
$streamReader = [IO.StreamReader]::new($outputStream, $outputEncoding)
# Read to the end, which will output our decompressed string
$streamReader.ReadToEnd()
# close up.
$streamReader.Close()

.NET and PowerShell

This has always been there, and it's pretty easy.

Both examples are less than 20 lines, with documentation.

These techniques are tried and true.

.NET has robust compression support because developers need to compress data all the time.

And therefore PowerShell has robust compression support.

If we build on top of simple PowerShell and .NET, we build in a way that lasts a lifetime.

When I said "I dusted off some old code" for Zippy, I wasn't kidding.

Zippy is an update of the Compress-Data and Expand-Data functions in Pipeworks, the first attempt of PowerShell as a web language.

This is 16-year-old code, with minor updates made to support multiple compression algorithms and improved piping.

My only regret is that I didn't spin this off into its own module long ago

You can use this article as a guide to implementing your own compression, or you can use a little module like Zippy to get the job done.

Please enjoy this new addition to your PowerShell toolkit, and have fun decompressing!

reddit.com
u/StartAutomating — 17 days ago

Search-Script -For ([type])

PowerShell is a pretty interesting language.

One of the ways it is interesting is that you can access the Abstract Syntax Tree. Another thing that's interesting is that you can convert any [ScriptBlock] into any [func].

Put these two together, and PowerShell can succinctly search itself.

That's the foundation of a simple little module I just updated, SearchScript

Let's learn how to search our scripts

How to Search Scripts

Most languages use an abstract syntax tree (AST) to represent the code you want to run. PowerShell is nice enough to let you easily access it.

Let's imagine we wanted to find out what types a script uses.

We could try to do this with regular expressions. We would not be happy. It's much easier to ask PowerShell.

We can access the Ast of any script block by using the .Ast property.

 {"hello world"}.Ast

We can get the members of any Ast by piping to Get-Member

 {"hello world"}.Ast | Get-Member

There's a couple of methods Find and FindAll. Find finds the first matching element. FindAll finds all of them (optionally recursively).

I almost always find myself using .FindAll, but they're both there if we need them.

FindAll takes a Func[Management.Automation.Language.Ast,bool] predicate (fancy speak for "condition").

But how do we make a Func?

We don't have to!

PowerShell does it for us. Let's see the nodes in a simple list:

{"hello","goodbye"}.Ast.FindAll({param($ast) return $true}, $true)

Let's do it again, but this time only find elements whose .Value is 'hello'

{"hello","goodbye"}.Ast.FindAll({param($ast) return $ast.Value -eq 'hello'}, $true)

How do we search scripts? We provide a [ScriptBlock] to find nodes within a [ScriptBlock].

This is quite handy! We can use this to find needles in haystacks.

Search-Script

We all love a useful function, so let's abstract this all a bit.

Search-Script is an eponymous module. It contains only one command, Search-Script (and a bunch of aliases to it).

All it accepts is:

  • A ``-Script` to search
  • Something to search -For
  • An optional [switch] for -Shallow searches

-For is a little special. We can accept multiple types of values for -For.

If it's a [ScriptBlock] we just call .FindAll.

If it's not a [ScriptBlock], we can make it into one.

Search-Script -For ([string])

If it's a [string], we'll try an exact match, unless it starts and ends with slashes.

Here's the current code:

# If `-For` is a `[string]`
if ($for -is [string]) {
    # the operator is -eq by default.
    $operator = '-eq'
    # If it takes the form of a regex literal 
    if ($for -match '^/.+/$') {
        # strip the slashes
        $for =
            $for -replace '^/' -replace '/$'
        # and match instead.
        $operator = '-match'
    }
    # Always double single quotes to avoid code injection.
    $For = $for -replace "'","''"
    # Create a `[Scriptblock]` that finds exactly that string.
    $for = [ScriptBlock]::Create("param(`$ast) (`$ast.Extent.ToString() $operator '$(            
        $For
    )') -or (`$ast.Value $operator '$For')")
}

Search-Script -For ([regex])

If it's a [Regex], we'll try to match it.

Here's the current code:

# If `-For` is a `[Regex]`
if ($for -is [Regex]) {
    $for =
        # Create a `[ScriptBlock]` that matches that pattern.
        [ScriptBlock]::Create("param(`$ast) `$pattern = [Regex]::new('$(
            # Always double single quotes to avoid code injection.
            $for -replace "'","''"
        )','$($for.Options)'); `$ast -match `$pattern")
}

Search-Script -For ([type])

If it's a [type], we'll try to find all instances of that type.

It's that last one that gets a little complicated.

Sure, we could just look for AST types. That would be easy. But we can also ask anything with a .TypeName to give us a type via reflection (and any static references will have a .StaticType). To make matters even more fun, equality comparison doesn't quite cut it for types. We have to check if a type is a subclass of a type. Oh, yeah, then there are interfaces. We have to check that if the type implements the interface.

It's just a bit more complicated than it's kin. Here's the current code:

if ($For -as [type[]]) {
    $for =
        # Create a `[ScriptBlock]` that looks for that type.
        # This one is more complicated, so we will create it in two parts 
        [ScriptBlock]::Create((
            (@(
                # dynamically create the list of types
                'param($ast)'
                "`$types = @("
                foreach ($forType in $for) {
                    $forType = $forType -as [type]
                    if (-not $forType) { continue }
                    "[$($forType.FullName)]"
                }    
                ")"     
            ) -join [Environment]::NewLine) + {
            # Find a reflected type, if there is one.
            $reflectedType = 
                if ($ast.TypeName.GetReflectionType) {
                    $ast.TypeName.GetReflectionType()
                } elseif ($ast.StaticType) {
                    $ast.StaticType
                } else {
                    $null
                }

            # Go over each of our potential types
            # Several conditions would be a use of our type
            foreach ($type in $types) {
                # * If the ast is that type, return true
                if ($ast -is $type) { return $true } 
                if (-not $reflectedType) { continue }
                # * If the reflected type is exactly that type, return true
                if ($reflectedType -eq $type) { return $true }
                # * If the reflected type is a subclass of that type, return true
                if ($reflectedType.IsSubClassOf($type)) { return $true }
                # * If the type is an interface,
                #   return true if the reflected type implements it    
                if ($type.IsInterface -and $reflectedType.GetInterface($type)) {
                    return $true
                }
            }
        # Returning nothing will be falsy, and will not return the element.
        }
    ))

The implementation might be a bit brutish, but the execution can be downright glorious.

# Find just the `[double]`
{1,2.0,3} | Search-Script -For ([double])

# Find just the `[int]`
{1,2.0,3} | Search-Script -For ([int])

# Find all the `[IComparable]` objects
{1,2.0,3} | Search-Script -For ([IComparable])

Using the Ast, we can find any needle in any scripted haystack. Please try to Search-Script and give feedback if you've got it.

Happy Hunting!

u/StartAutomating — 18 days ago

The Power of Primes

Prime numbers are pretty powerful.

That's why I just released a new PowerShell module based off of an old mathematical concept: PrimeTime.

PrimeTime uses prime numbers as time intervals.

Let's learn how this helps

Prime Number Primer

Prime Numbers can only be divided by themselves and one.

This makes primes pretty rare.

Prime numbers are particularly useful in programming, but it's not always obvious why or how.

A lot of people might vaguely point towards cryptography as the prime real estate for prime utility.

The thing of it is, if you're writing your own cryptography, you're probably doing it wrong.

Let's talk about a more practical application of primes.

The Cicada Principle

In North America there is a curious critter known as the periodical cicaca.

For the vast majority of their long lifespans, they live underground.

Once every N years, they surface in mass to start the next generation.

That N is a prime.

Why?

Cicadas come out en masse so that there are too many of them to eat.

Millions of little critters have to have a perfectly timed multi-year internal clock in order to make this work.

If two cicadas of different intervals produced offspring, their children might have a messed up internal clock, and come out of the ground at the worst time.

So there's an evolutionary advantage to cicadas coming out in large batches, as long as another cicade brood isn't doing the same thing at the same time.

Which brings us back to primes.

Primes are relatively rare.

So are products of primes (at least past the first few)

Let's take two primes as an example.

Imagine one brood of cicadas came out every 11 years, and another brood came out every 13 years.

We can find out how long it will take for these two broods to come out at the same time by simply multiplying the primes.

11 * 13 -eq 143

So, with just two relatively low primes, we have an overlap every 143 years.

This is how primes are most useful to programming: they rarely overlap.

Sieve of Eratosthenes

This has been known for much longer than computers have existed.

Imagine we wanted to find prime numbers quickly.

We can do this by constructing a sieve that filters out any non-prime number.

This is called the Sieve of Eratosthenes

Once we know 2 is prime, we know every other even number is not prime.

Once we know 3 is prime, we know every third number is not prime.

To quickly get prime numbers up to a point, we can use this little PowerShell filter

# Calculate primes reasonably quickly with the Sieve of Eratosthenes
# Pipe in any positive whole number to see if it is prime.
filter prime {
    $in = $_
    if ($in -isnot [int]) { return }
    if ($in -eq 1) { return $in }
    if ($in -lt 1) { return}
    if (-not $script:PrimeSieve) {
        $script:PrimeSieve = [Collections.Queue]::new()
        $script:PrimeSieve.Enqueue(2)
    }


    if ($script:PrimeSieve -contains $in) { return $in}
    foreach ($n in $script:PrimeSieve) {
        if (($n * 2) -gt $in) { break }        
        if (-not ($in % $n)) { return }
    }
    $script:PrimeSieve.Enqueue($in) 
    $in
}

Prime Animations

Imagine we want a vibrant page. We want things to keep changing yet feel unpredictable. All we need to do is use different prime intervals.

The PrimeTime logo animates eight primes:

7 * 11 * 13 * 17 * 19 * 23 * 29 * 31

The logo will repeat every 6685349671 seconds, or almost 212 years.

The PrimeTime page background uses 56 primes.

This background will repeat every 8.84753141993573E+116 seconds.

That's exponential notation.

This is a mind-boggling large number (so large it overflows the .NET [TimeSpan]).

Turn that interval into years and it's still mind-boggling.

The page background will repeat every 100 billion years

Performance and Scheduling

Imagine we want to design a system that's constantly checking for problems.

We want the system to know about problems as soon as we can, but nobody's exactly sure how often they need to check for something.

If we go around and ask our colleagues "how often should we can scan for this?", the response if often a shrug 🤷.

Often, people will pick an arbitrary number that seems reasonable. Let's say every 5 minutes, 10, or 15 minutes.

Are we starting to see the problem here?

Every 5 minutes, every computer in the cloud starts to collect stats and report them back.

And we get a traffic jam.

Every 10 minutes, more computers in the cloud collect more data, and our traffic jam gets worse.

Every 15 minutes, even more computers collect even more data, and our traffic jam puts your average freeway to shame.

Left to our own intuition, we create problems for ourselves and our organizations.

Each individual query is small, but because we're doing so many at once, it can grind performance to a halt.

By the way, this isn't a hypothetical.

Long long ago, the Office365 team asked me to make some monitoring software to help improve internal visibility into the datacenters.

Everyone asked for 5, 10, or 15 minute intervals. ~100 different metrics were collected from ~30000 machines.

And the first time we tried it on everything, the traffic jam ensued.

That's when I first realized the power of primes.

I made three slight adjustments to the timeframes:

  • Every 5 minutes became every ~7 minutes
  • Every 10 minutes became every ~11 minutes
  • Every 15 minutes became every ~17 minutes

Now, instead of having a traffic jam every 5 minutes, things smoothed out.

  • A small traffic jam would occur every ~77 minutes (7*11)
  • Another small traffic jam would occur every ~119 minutes (7*17)
  • Another small traffic jam would occur at ~187 minutes (11*17)
  • All traffic could jam every ~1309 minutes (7*11*17)

Note the tildas.

The real trick came in by using prime intervals in both minutes and seconds and using a random delay on the tasks to ensure they didn't all start at once.

This took the system from something that could derail a datacenter to something that could monitor thousands of machines while barely impacting performance.

This is the power of primes.

Hope this helps!

u/StartAutomating — 1 month ago

Special PowerShell User Group Meeting Tonight (7/8)

Tonight there's a special meeting of the Pacific PowerShell User Group.

Bruce Payette will be speaking about Braid.

Bruce is one of the original authors of the PowerShell language.

Braid is a superfast scripting language he's been building.

Recently, Bruce added a number of features to use Braid seamlessly from PowerShell.

Stop by to learn about this new language and see some of the stuff Braid can do.

Party starts @ 6:00 pm pacific time.

Note: This is a virtual user group.

Just join on Meetup and you will get the meeting link.

reddit.com
u/StartAutomating — 1 month ago
▲ 57 r/regex+1 crossposts

RegEx -replace

PowerShell has all sorts of fun features, including a ridiculous number of operators.

One amazing under-sung heros of PowerShell is the -replace operator.

It lets us replace content with regular expressions.

It's easier to use than you'd think.

Regular expressions are less scary in small doses, and chaining -replace operators lets us attack the problem step by step.

Chaining -replace

Let's take a simple problem as an example.

Imagine we wanted to make a consistent file name pattern out of a string

We might want to start by replacing whitespace with dashes

"This Is A Title!" -replace '\s', '-'

That leaves our exclamation point at the end. We probably don't want any punctuation. We can avoid that with the somewhat humorously named character class: \p{P}. We can remove all repeated punctuation by adding a +: \p{P}+

One more replace:

"This Is A Title!" -replace '\p{P}+' -replace '\s', '-'

The line is starting to get a little long. Fun fact: you can spread operators across multiple lines.

Let's add comments while we're at it

"This Is A Title!" -replace # Replace any punctuation,
    '\p{P}+' -replace # then replace any whitespace with dashes.
    '\s', '-' 

Let's go for one more bonus trick. PowerShell lets you convert script blocks to event handlers. Let's lowercase all the letters (\p{L}).

On PowerShell Core, we can do this:

"This Is A Title!" -replace # replace any punctuation
    '\p{P}+' -replace # then replace any whitespace with dashes
    '\s', '-' -replace # then lowercase any letters
    '\p{L}+', {"$_".ToLower()}

There's an absurdly amazing amount of stuff you can do with -replace, but there's at least one more trick we have to cover: substitutions.

-replace with substitution

I'm pretty sure I'd have to give up my "RegEx guru" badge if I didn't mention at least one more thing you can do with -replace: substitutions.

.NET Regular expressions are two domain specific languages. Regular expressions match and extract text. Regular expression substitutions replace matches.

For example, let's suppose we have a number of emails, and we want them in domain/username format.

First we'll want to make a quick and dirty email regex, using a "named capture" to get the username and domain.

'someone@example.com' -match '(?<username>\S+)@(?<domain>\S+)'

Then, we can -replace the email with just the domain/username.

'someone@example.com' -replace 
    '(?<username>\S+)@(?<domain>\S+)', '${domain}/${username}'

This format might look like PowerShell variables, but it actually predates them by years. Search for "Regular Expression Substitutions" if you want to learn more about the syntax. It's got quite a few tricks up it's sleeve.

Irregular

RegEx can be scary. I used to be terrified of it, too.

If you aren't too comfortable with Regular Expressions, that's pretty normal. A while back I wrote a module called Irregular that makes regular expressions strangely simple.

It's got a lot of example regular expressions in there, and one handy function for creating RegEx. New-RegEx is your friend.

Do you already use -replace? Have you done cool things with regular expressions in PowerShell? Share 'em if you've got em.

Want to learn more about regular expressions in PowerShell? Just ask.

u/StartAutomating — 2 months ago

Stop using [System]

I'm getting old enough that my fingers hate my lifetime of programming.

I'll save a few keystrokes where I can.

There's something simple most people don't seem to know about PowerShell syntax.

It saves seven characters of typing every you use this, and runs a tiny bit faster.

You never need to specify stuff is in the [System] namespace.

Stop Using [System]

.NET is a huge framework with tons of useful stuff in it. There's a lot of stuff in the System namespaces. Built-in framework functionality often exists in one of the many namespaces in System.

By the time PowerShell was being built, it was pretty clear that leveraging .NET was worth it, and that most people wouldn't want to type six to seven more characters every time.

So, since PowerShell v1, you haven't had to.

You can omit the [System] in any type in any system namespace

So instead of:

 [system.collections.generic.list[string]]

We can write:

 [collections.generic.list[string]]

Instead of:

 [System.Collections.IDictionary]

We can write:

 [Collections.IDictionary]

This is true for every system type. On my machine, there are 4722 public types in the system namespace. That's 33054 characters I will never have to type.

It makes scripts shorter and simpler to read.

Also, when PowerShell resolves types, it checks for the shorter names first. This saves a very tiny amount of time in each of your scripts. (I was corrected)

Yet, sadly, I see the system namespace everywhere in people's scripts.

I beg of you all:

  • Save your fingers
  • Make scripts shorter

Stop Using [System]

reddit.com
u/StartAutomating — 2 months ago

Simple Splatting and the GitHub CLI

Brevity may be the soul of wit, and I may be bad at it.

Let's try to make a quick post about a little daily PowerShell timesaver: splatting the GitHub CLI.

I'm going to show you how you can save typing and time with the GitHub CLI.

What is Splatting?

Splatting is a simple technique in PowerShell. It lets you pass multiple parameters. It's been there since PowerShell version 2. This is old, consistent technique.

Most people are used to splatting a dictionary, like:

# You can splat a dictionary
$MyId = @{id=$pid}
Get-Process @MyId

This is a cool and useful technique, and most people overlook the other half of splatting:

# You can splat a list
$allIssues = @('--state', 'all', '--limit, '2kb')
gh issue list @allIssues

The Trick

You just saw it.

There are millions of apps you could use this trick with.

I just happen to use the github cli on most days.

You're trading typing all of those arguments for typing a shorter string.

It saves seconds every time you use it.

And it's one simple line.

You can stick it in your profile and every time you're using PowerShell, you'll have those variables to use.

You can just type this sort of trick in if you find yourself using the same parameters.

Think of it as a preset of parameters. Because that's basically what it is.

Multiple Splats

It's important to know that you can do multiple splats.

Here's a simple example:

$bug = @('--label', 'bug')
$assignMe = @('--assignee', '@me')

gh issue create --title 'Some Issue' --body 'Some problem' @assignMe @bug

This would create a bug.

Let's make another, longer one:

# Get issue information as json.
$IssueAsJson= @('--json', (
    'assignees','author','body','closed','closedAt',
    'closedByPullRequestsReferences','comments','createdAt',
    'isPinned','labels','milestone','number','reactionGroups',
    'state','stateReason','title','updatedAt','url' -join ','    
))

$allIssues = @('--state', 'all', '--limit, '2kb')
$IssueList = gh issue list @allIssues @issueAsJson | ConvertFrom-Json
$issueList

If you run this, you

  • List all of the issues from a repo as json
  • Convert them from json into objects

Feel free to copy/paste these tricks into your profile. They're handy!

reddit.com
u/StartAutomating — 2 months ago
▲ 47 r/obs+1 crossposts

Scripting your Streams with obs-powershell

OBS is awesome! It's a real-time audio video mixer that can stream or record, and it's all open source!

The nerd in me kinda fell in love with OBS a few years ago, and then I discovered they had a WebSocket API 🤯.

We can control almost anything OBS does in the blink of an eye, from any language. That's how I ended up building obs-powershell, an open-source PowerShell module for OBS.

obs-powershell uses the websocket to automate OBS. We can script anything obs allows.

Just import and Connect-OBS and we're off to the races!

Here's a brief taste of what's possible:

# Show-OBS lets you show all sorts of things.
# It will return a scene item.
$Stars = Show-OBS -Uri "https://pssvg.start-automating.com/Examples/Stars.svg"
Start-Sleep -Milliseconds 50
# We can .Hide/.Disable scene items
$Stars.Hide()
Start-Sleep -Milliseconds 50
# We can .Show/.Enable scene items
$Stars.Show()
Start-Sleep -Milliseconds 50
# We can make an item small
$Stars.Scale(0.1)
Start-Sleep -Milliseconds 50
# We can fit it to the screen
$stars.FitToScreen()
Start-Sleep -Milliseconds 50
# and we can make it big again, with an animation
$Stars.Scale("1%","100%","00:00:01")
Start-Sleep -Seconds 1

# We can do even more broad animations, like moving things across the screen.
$Stars.Animate(@{
    X = "-25%"
    Y = "50%"
    Scale = "20%"
}, @{
    X = "125%"
    Y = "50%"
    Scale = "50%"
    Rotation = 180
}, "00:00:05")
Start-Sleep -Seconds 1

obs-powershell has a pretty rich object model. Remember, you can always pipe objects into Get-Member to see what they can do.

We can create and destroy scene items, adjust filters, animate transforms, and far too much more to list: all with simple scripts. There's also support for various plugins, including Exceldro's excellent obs-shaderfilter.

Script your streams! If you have cool ideas, please share. If you have tricky OBS automation questions, please ask.

u/StartAutomating — 2 months ago
▲ 22 r/fractals+1 crossposts

Turtles in a PowerShell

A while back I finally figured out how to make Turtle Graphics in PowerShell.

I wrote a pretty fun module for it, Turtle. At this point there's a silly amount of stuff you can do with this, including infinite art generation and data visualizations.

The topic came up again today, and so I thought I'd take a quick second to show the secrets of the ooze to the community.

Here's an example of a really minimal Turtle. All it does is: .Rotate() by an angle, Move .Forward() a distance, and let us change if the pen is down with .PenDown.

#Define our custom object 
$turtle = [PSCustomObject]@{
    Heading = 0.0
    Steps = @()
    PenDown = $true
}

#Add a Rotate and Forward method, and a PathData script property
$turtle | 
    Add-Member ScriptMethod Rotate {
        param([double]$Angle)
        # Turn by the angle
        $this.Heading += $angle
        # and return ourself.
        return $this
    } -Force -PassThru |
    Add-Member ScriptMethod Forward {
        param([double]$Distance)
        #Any move of the turtle is just a polar coordinate.
        #We turn the Distance@Heading into x,y with some trig
        $x = $Distance * [math]::cos($this.Heading * [Math]::PI / 180)
        $y = $Distance * [math]::sin($this.Heading * [Math]::PI / 180)
        #If the pen is down, we draw a relative line (`l`)
        #If the pen is up, we move (`m`)
        $letter = if ($this.PenDown) { "l" } else {"m" }
        #Add the step
        $this.Steps += "$letter $x $y"
        #Return ourselves.
        return $this
    } -Force -PassThru |
    Add-Member ScriptProperty PathData {
        return "m 0 0 $($this.Steps)"
    }        
        
# Make a basic triangle 
$turtle.
    Forward(42).Rotate(120).
    Forward(42).Rotate(120).
    Forward(42).Rotate(120)

# Put our path data into an XML
$svg = [xml]"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 42 42' width='100%' height='100%'>
    <path d='$($turtle.PathData)' />
</svg>"

$svg.Save("$pwd/triangle.svg")

That's ~40 lines for a simple Turtle Graphics engine (with docs)!

Of course, we can make our Turtle much smarter by adding more and more moves. All we need to do is add more and more methods. That's what the module gives us: a pretty smart Turtle with lots of methods and properties. Play around, it's fun!

Turtle Graphics are pretty great! Hopefully this helps make it clear to everyone how simple and easy they can be.

u/Leading_Bandicoot358 — 2 months ago
▲ 5 r/svg

Turtle Graphics in SVG with PowerShell

Last summer, I figured out how to build Turtle Graphics engine.

In Turtle Graphics, we start off with three primitive moves:

  • We can move forward
  • We can rotate
  • We can lift out "pen"

Using these three basics primitives, we can draw any image.

We can imagine our steps as an SVG Path (sometimes known as Path2D)

I like doing WebDev with PowerShell, so I made a Turtle that generates SVG and HTML.

It got pretty cool.

Please take a look at the demo page for Turtle. There are over 130 examples, and at least a quarter of them are mind-blowingly cool.

The project is open source, and is on GitHub

We can draw beautiful images with simple scripts, including fractals the Sierpinski Triangle.

For example, this code:

 turtle sierpinskitriangle 42 4 stroke '#224488' fill '#4488ff' save ./sierpinskitriangle.png

Generates a Sierpinski Triangle in SVG and saves it to png.

https://preview.redd.it/8hgw08febk8h1.png?width=672&format=png&auto=webp&s=cbd93d97681d1baf0aece0fd8b230f74e1fd9726

Turtle has been really fun to build and really fun to play with.

Thoughts and feedback are welcome and appreciated.

reddit.com
u/StartAutomating — 2 months ago
▲ 7 r/webdev

Turtle Graphics in SVG and HTML with PowerShell

Last summer, I figured out how to build Turtle Graphics engine.

In Turtle Graphics, we start off with three primitive moves:

  • We can move forward
  • We can rotate
  • We can lift out "pen"

Using these three basics primitives, we can draw any image.

We can imagine our steps as an SVG Path (sometimes known as Path2D)

I like doing WebDev with PowerShell, so I made a Turtle that generates SVG and HTML.

It got pretty cool.

This Showoff Saturday, I'm sharing the demo page for Turtle.

The project is open source, and is on GitHub

We can draw beautiful images with simple scripts, including fractals the Sierpinski Triangle.

For example, this code:

 turtle sierpinskitriangle 42 4 stroke '#224488' fill '#4488ff' save ./sierpinskitriangle.png

Generates a Sierpinski Triangle in SVG and saves it to png.

Sierpinski Triangle

Turtle has been really fun to build and really fun to play with.

Thoughts and feedback are welcome and appreciated.

reddit.com
u/StartAutomating — 2 months ago

Friday Fun - Making Memes with PowerShell

It's Friday. Let's have some Fun!

Let's write fun servers in PowerShell.

Fun Servers

About a week ago, I released Fun. It's a fun functional server in PowerShell.

It's free, open-source, and lots of fun to play with.

It lets us write servers in PowerShell by starting functions with /

For example:

function / { "<h2>Now Serving from PowerShell</h2>" }

We can also make a server return a content type. Just add the [OutputType()] attribute.

function /d20 {
    [OutputType('text/plain')]
    param()
    Get-Random -Min 1 -Max 20
}

I think this is a short, simple, and sweet way to represent an endpoint.

Looks Good to Me (🤞 looks good to you, too).

To start this server, we can just:

Start-Fun

Fun supports live reloading, so we can add new endpoints to our server just by adding new functions.

Last week's post introduced the module and showed how we could implement our own Fun server from scratch.

For this week's fun, let's make some memes

Making Memes with PowerShell

What's in a Meme? Technical answers only.

We can think a meme as a combination of:

  1. An image
  2. Some Text
  3. An (optional) animation

This a pretty easy function to write.

All we need to do is output a page with an image, some text, and an animation.

For bonus points we might want to allow you to choose a Google Font and customize the CSS.

Let's take a crack at it:

function /meme {
    <#
    .SYNOPSIS
        Fun /meme
    .DESCRIPTION
        Making Memes with PowerShell
    .EXAMPLE
        /meme "https://media.tenor.com/PaU1GnUGnfAAAAAC/oprah.gif" "You Get a Meme" > ./YouGetAMeme.html
    .LINK
        Start-Fun
    #>
    [OutputType('text/html')]
    param(
    # The Meme Image
    [string]
    $Image = 'https://media1.tenor.com/m/DMlZVvfAsMQAAAAC/boromir-lord-of-the-rings.gif',

    # The Meme Text
    [string]
    $Text = $(
        "One Does Not Simply " + (
            "Walk into Mordor",
                "Make a Server",
                "Make a Meme",
                "Write Some Code" | 
                    Get-Random
        ) 
    ),

    # The style used to render each layer
    [string[]]
    $LayerStyle = @(
        "position: absolute"
        "display: grid"
        "place-items: center"
        "width:100%"
        "height:100%"
    ),

    # The style used to render our text
    [string[]]
    $TextStyle = @(
        'color: white'
        'font-size: 2.5rem'
        'place-items: center'
        'text-align: center'
        'width: 100%'
        'translate:0% 40%'
    ),

    # An optional background. 
    [string]
    $Background,

    # The Google Font name.  By default, Roboto.
    [Alias('FontName')]
    [string]
    $Font = 'Roboto',
    
    # The animation.  By default, scales the meme from 0 to 1.
    [string]
    $Animation = "@keyframes animate-meme { from {scale: 0} to { scale: 1} }",

    # The animation duration.  By default, 0.666 seconds.
    [TimeSpan]
    $AnimationDuration = '00:00:0.666'
    )
    
    @(
    "<html>"
        "<head>"
            # Make our page title our meme text
            "<title>$([Web.HttpUtility]::HtmlEncode($text))</title>"
            # Load a font if we've got one
            if ($Font) {
                "<link rel='stylesheet' href='https://fonts.googleapis.com/css?family=$Font' id='font' />"
            }

            # Set up our base styles
            "<style>"
                "body {
                    max-width: 100vw;
                    height: 100vh;
                    display: grid;
                    place-items:center;
                    margin:0;
                    padding:0;
                    box-sizing: border-box;
                    position: relative;
                    $(if ($Background) { "background: $background;" })                    
                }"
                # Make images fill available space
                "img { width: 100%; height: 100%; }"
                
                if ($Animation -and # If we have an animation
                    # and can extract the animation name           
                    $Animation -match '@keyframes\s(?<name>[\w-]+)') {
                    # Include the animation css
                    $Animation
                    # and make a class to apply the animation
                    ".animated {
                        animation-name: $($matches.name);
                        animation-duration: $($AnimationDuration.TotalSeconds)s; 
                        animation-repeat-count: indefinite;
                    }"    
                }
                
                # Make a class to style our layer
                ".layer { $($LayerStyle -join ';') }"
                # and make one more class to style our text
                ".text { $(
                    ($TextStyle + "font-family:'$font'") -join ';'
                )}"                
            "</style>"
        "</head>"        
        "<body>" 
            # The page has a background layer           
            "<section class='layer animated'>"
                # (containing our image)
                "<img src='$Image' />"
            "</section>"
            # and a text layer
            "<section class='layer text animated'>"
                # containing our encoded text
                [Web.HttpUtility]::HtmlEncode($text)
            "</section>"
        "</body>"
    "</html>"
    ) -join "`n"
}

Import that function, run Start-Fun, browse to /Meme, and enjoy the show.

If we want to customize the input, we can just provide query parameters.

We can also run this outside of the browser to make a meme html file.

This is just a PowerShell function, so we can also run it locally and redirect to a file.

/Meme > ./OneDoesNot.html

We can also provide parameters if we want.

/meme "https://media.tenor.com/PaU1GnUGnfAAAAAC/oprah.gif" "You Get a Meme" > ./YouGetAMeme.html

You Get a Meme. And You Get a Meme. You all Get a Meme!

Fun PowerShell

PowerShell can be a lot more fun than just systems management scripts.

Have you made any Fun servers with PowerShell yet? Share em if you've got em.

I'll be making more memes now that I've got a function for it, and I continue to have way too much fun with PowerShell.

Good luck! Have Fun! Don't Die!

I hope this helps, and I'll see you next week.

u/StartAutomating — 2 months ago
▲ 30 r/PowerShell+1 crossposts

Friday Fun Servers with PowerShell

I've been working on WebDev with PowerShell for a while now.

I find it a lot of fun.

I'm somewhat obsessed with making things easy in PowerShell, and trying to make development fun.

I was writing a long post on writing servers with PowerShell, and I wanted to close it with something fun: using the function name as a route.

Fun Servers

What do I mean?

Functions in PowerShell can be named just about anything.

For example:

function / { "<h1>Hello world</h1>" }

Totally legal and valid PowerShell function name. Obvious. Short. Simple. Sweet.

For a bit more fun, we can use [OutputType] to provide a ContentType

function /main.css {
    [OutputType('text/css')]
    param()
    "body { max-width: 100vw; height: 100vh; font-size: $(Get-Random -Min 1.0 -Max 2.5)rem} "
}

I don't know about you, but I feel like this is a fun approach.

I started to write up a good example, but then I kept having fun with it.

And now there's a fun new open-source PowerShell module: Fun

This fun module lets you quickly and easily create servers that use this pattern:

Simply declare functions or aliases named /*, then Start-Fun.

With this module, functions run as you, in the current context and host.

This means it can do anything you can do in PowerShell.

It can create very fun interactions between your terminal and your browser.

Query strings are also automatically mapped to function parameters.

This module and this approach is, quite frankly, lots of fun.

A Simple Fun Server

If you don't want to use a module, here's a brief example of how to make your own fun server.

This code doesn't include all the bells and whistles of the Fun module, but it shows how simple function routing can be.

$InitializationScript = {
    function / {
        <#
        .SYNOPSIS
            Root page
        .DESCRIPTION
            Randomized Root Page
        #>
        [OutputType('text/html')]
        param()
        "<html>"
            "<head>"    
                "<link rel='stylesheet' href='/main.css' />"                    
            "</head>"
            "<body>"
                "<p class='animated'>"
                    "Hello World", "Hello", "Hi", "Welcome", "Wow" | Get-Random
                "</p>"
            "</body>"
        "</html>"
    }

    function /main.css {
        <#
        .SYNOPSIS
            /main.css
        .DESCRIPTION
            Just dynamically defining a css file.
        #>
        [OutputType('text/css')] # (the output type determines the content type)
        param()

        # We can just output css blocks
        "@keyframes zoom-from-random { 
            0% {
                translate:$(
                    Get-Random -Min -50 -Maximum 50
                )vw $(
                    Get-Random -Min -50 -Maximum 50
                )vh;
                scale:2;
            }
            100% {
                translate: 0 0;
                scale: 1;
            }
        }"
        
        ".animated { animation-name: zoom-from-random; animation-duration: $(Get-Random -Min 250 -Max 2500)ms;}"
        "h1 { text-align: center; }"

        "body { max-width: 100vw; height: 100vh; display: grid; place-items: center; font-size:$(Get-Random -Min 2.0 -Maximum 10.0)rem }"
    }        
}



# Create a listener.
$listener = [Net.HttpListener]::new()
# Add prefixes for a local random port.
$listener.Prefixes.Add("http://127.0.0.1:$(Get-Random -Min 5kb -Max 50kb)/")
# Start the listener.
$listener.Start()

# Write our a warning so we know we're serving and have something to click
Write-Warning "Listening on $($listener.Prefixes)"


# Start our background job
Start-ThreadJob -ScriptBlock {
    # pass it the http listener
    param($listener, $mainRunspace)

    # While the listener is listening, 
    while ($listener.IsListening) {
        # get the next context
        $context = $listener.GetContext()
        $request, $response = $context.Request, $context.Response
        
        $requestedFunction = 
            $ExecutionContext.SessionState.InvokeCommand.GetCommand(
                $request.Url.LocalPath,
                'Function,Alias'
            )            
        
        if (-not $requestedFunction) {
            $response.StatusCode = 404
            $response.Close()
            continue
        }

        if ($requestedFunction.OutputType) {
            $response.ContentType = $requestedFunction.OutputType.Name -join ';'
        }

        $reply = & $requestedFunction 2>&1

        if ($reply.ErrorRecord) {
            $response.StatusCode = 500                
        }
        if ($reply -as [byte[]]) {
            $response.Close(($reply -as [byte[]]), $false)
        }
        else {
            $response.Close([Text.Encoding]::UTF8.GetBytes("$reply"), $false)
        }
    }
} -ArgumentList $listener, (
        [runspace]::DefaultRunspace
) -ThrottleLimit 16kb -Name "$($listener.Prefixes)" -InitializationScript $InitializationScript |
    # Add our listener to the job, so we can easily tell the job to stop listening
    Add-Member NoteProperty HttpListener $listener -Force -PassThru

That's about 100 lines for a functional server. Not too shabby

Friday Fun Servers

I think functional servers are short, simple, sweet, and, well, Fun.

I'll be trying to make a habit of Friday Fun examples.

What do you think? Want to join me?

Please give this approach a try.

Have Fun!

u/StartAutomating — 2 months ago

Good PowerShell User Groups?

What Good PowerShell User Groups are there?

Are they in-person or virtual?

Are there upcoming events we should all know about?

reddit.com
u/StartAutomating — 2 months ago

Events are Easy

Events are easy.

Events let you know when something happened, and respond to it if you choose.

Events are incredibly useful.

Why?

Because they let you run what you want, when you want.

Let's see how simple they are:

Creating Events

Events are easy to create.

To make a new event, simply run:

New-Event MyCustomEvent

This will output an event object.

If nothing subscribes to the event, the event will go in the queue

We can get events with:

Get-Event

We can handle these events whenever we want.

How about now?

Subscribing to Events

We can run code the millisecond something happens.

To do this, we can subscribe to the event.

There are two types of events we can subscribe to in PowerShell:

Engine events and object events.

Engine Events

We can create engine events with New-Event.

We can subscribe to engine events with Register-EngineEvent

$subscriber = Register-EngineEvent -SourceIdentifier "Hello World" -Action {
    "Hello World" | Out-Host
}
$helloWorld = New-Event -SourceIdentifier "Hello World" 

You might notice a cool thing here: An event's "Source Identifier" can be whatever we want.

Let's pass along a message:

$subscriber = Register-EngineEvent -SourceIdentifier "Print Message" -Action {
    $event.MessageData | Out-Host
}
$printMessageEvent = New-Event -SourceIdentifier "Print Message" -MessageData "Hello World"

If you run these scripts multiple times, you'll quickly notice that multiple subscriptions are allowed.

The cool thing to note here is that event subscribers share data in their $event.MessageData

Let's demonstrate this by counting twice.

$doubleCounter = foreach ($n in 1..2) {
    Register-EngineEvent -SourceIdentifier "Counter" -Action {
        $event.MessageData.Counter++
        $event.MessageData.Counter | Out-Host
    }
}

$counterEvent = New-Event -SourceIdentifier "Counter" -MessageData @{
    Counter=0
}

Every time we run this block of code, we get two more subscriptions and a bunch more output.

Before we clean up, let's talk about object events

Object Events

PowerShell is built on the .NET framework. .NET already has events all over the place.

Let's start simple, with a timer:

# Create a timer
$timer = [Timers.Timer]::new([Timespan]"00:00:03")
# don't automatically reset (we only want to do this once)
$timer.AutoReset = $false

# Subscribe to our event
$inAFew = Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action {
    "In a few seconds" | Out-Host
}

# Start the timer (see a message in a few seconds)
$timer.Start()

Lots of .NET types have events.

To see if any object supports events, simply pipe it to Get-Member (events will be near the top).

Timers are a good start. What about watching for file changes?

$watcher = [IO.FileSystemWatcher]::new($pwd)

Register-ObjectEvent -InputObject $watcher -EventName Changed -Action {
    $changedFile = $event.SourceArgs[1].Fullpath
    $changedFile | Out-Host
    $changedFile
} 

'Check this out' > ./What-File-Changed.txt

This is just the tip of the iceberg.

There are literally millions of .NET types out there.

They can all have events.

And we can subscribe to these events in PowerShell

Getting Subscribers

Let's start to clean up a bit:

To get any current subscribers, we can use Get-EventSubscriber

Get-EventSubscriber

To get events subscribing to a source, we can use:

Get-EventSubscriber -SourceIdentifier "Hello World"

If a subscriber has an .Action, we can get results of that action by piping to Receive-Job

This pipeline will get any output from any subscriber with an action

Get-EventSubscriber |
    Where-Object Action |
        Select-Object -ExpandProperty Action |
            Receive-Job -Keep

Hopefully this will help make another part of event subscriptions "click":

Not only can we run code in the background: we can easily get the results, too.

Cleaning Up

We can unsubscribe by using Unregister-Event

# Unsubscribe from everything
Get-EventSubscriber | Unregister-Event

While we're cleaning up, let's also take care of any events in the queue.

We can do this with Remove-Event

# Get all events, and remove them.
Get-Event | Remove-Event

Now that we've cleaned up our runspace, let's clean up this post and review what we've learned:

Events are Easy

  • Events are Easy to create (New-Event)
  • Events are Easy to list (Get-Event)
  • Events are Easy to remove (Remove-Event)
  • Events are Easy to subscribe to (Register-EngineEvent)
  • Events are Easy on any object (Register-ObjectEvent)

Events are Easy!

Give them a try.

Eventually, you'll find events are excellent tools of the trade.

reddit.com
u/StartAutomating — 3 months ago
▲ 125 r/Markdown+1 crossposts

Mastering Markdown with PowerShell

I've loved Markdown since the day it was a Daring Fireball post.

It's a simple rich text format that gets the job done, and it's used everywhere.

Markdown in PowerShell

Markdown is supported out of the box on PowerShell 6+, using the ConvertFrom-Markdown command.

Here's it in action:

"# Hello World" |
    ConvertFrom-Markdown |
    Select -Expand HTML

Like any other page in a static site, Markdown is just text.

And PowerShell is Pretty Good at manipulating text.

To make PowerShell that outputs markdown, just make simple scripts that spit out text.

Markdown Static Sites

One very simple use of this technique is making static sites with Markdown.

If we don't want to worry about look and feel too much, we can do this with the following pipeline:

"# Markdown" | 
    ConvertFrom-Markdown | 
        Select-Object -ExpandProperty Html >
            ./markdown.html

If we wanted to make a page for every file in the directory, we could:

foreach ($file in Get-ChildItem *.md -File) {
    ConvertFrom-Markdown -LiteralPath $file.Fullname |
        Select-Object -ExpandProperty Html > (
            $file.Fullname -replace '\.md$', '.html'
        )
}

That's a static site generator in six lines of PowerShell!

Here's an even shorter version:

foreach ($file in Get-ChildItem *.md -File) {        
    $html = (ConvertFrom-Markdown -Path $file.Fullname).html
    $html > ($file.Fullname -replace '\.md$', '.html') 
}

Now we've got a static site generator in four lines!

Static Sites are Simple (with PowerShell).

To make websites in PowerShell, all we need to do is loop over markdown and optionally add some layout.

Making Markdown

We can make markdown in PowerShell by just outputting text.

@(
    "# Hello World"
    "## How Are You?"
    "Today is $([DateTime]::Now.ToShortDateString())"
) > ./example.md

Each line of output will become a line in the markdown file.

We can use conditionals if we want to. Let's switch it up by including the day of week.

@(
    "# Hello World"
    switch ([DateTime]::Now.DayOfWeek) {
        Monday { "Just Another Manic Monday "}
        Tuesday { "Taco Tuesday" }
        Wednesday { "Halfway thru the week! "}
        Thursday { "Almost Friday" }
        Friday { "Happy Friday! "}
        Saturday { "It's the weekend!"}
        default { "It is $([DateTime]::Now.DayOfWeek)" }
    }
) > ./example.md

Making Markdown with Functions

We can make functions that output markdown.

Here's a simple one that outputs headings

function markdown.heading {
    param(
        [string]$Message = 'Hello World',
        [ValidateRange(1,6)]$Level = 1
    )
    # Multiply our heading character by our level
    # and put a space in between the heading and message
    ('#' * $level), $Message -join ' ''
}

markdown.heading "Markdown Functions" 
markdown.heading "Are just functions" -Level 2
markdown.heading "That output markdown" -Level 3

Since markdown functions are just PowerShell functions, we can put whatever we want in there.

function markdown.get.process {
    # Markdown tables have a header row
    "|Name|Id|"
    # Followed by a row that aligns text
    "|:-|-:|"
    # Followed by any number of rows of data
    foreach ($process in Get-Process) {
        '|' + (
            $process.Name, $process.Id -join '|'
        ) + '|'
    }
}

markdown.get.process > ./process.md

Now we hopefully see how easy it is to make markdown in PowerShell.

Just spit out strings.

This is already probably cool enough, but why not make markdown into something we can query?

Making Markdown into XML

ConvertFrom-Markdown converts Markdown into HTML.

It's just a hop, skip, and a jump to make this markdown into XML.

Because all of our tags are perfectly balanced, we can make markdown in XML by just putting it into another element.

Cannonically, I prefer putting markdown into an <article> element

@(
    "<article>"
    ("# Hello World" | ConvertFrom-Markdown).html
    "</article>"
) -join '' -as [xml]

That's it! We've turned a easy old markdown into hard-to-write XML.

Why is this useful?

Because now we can query markdown.

Markdown, XML, and XPath

To show this in action, let's start really simple:

Let's just get all of the nodes in some markdown

@(
    "# Hello World"
    "## Don't mind me"
    "### Just about to turn markdown into XML"
    "> This is pretty cool, right?"
) -join [Environment]::Newline |
    ConvertFrom-Markdown |
    Foreach-Object {
        "<article>$($_.Html)</article>" -as [xml]
    } |
    Select-Xml //*        

Let's get all link hrefs in some markdown:

# Make some markdown
@(
    "# Some Links"
    "* [StartAutomating on GitHub](https://github.com/StartAutomating/)"
    "* [PoshWeb on GitHub](https://github.com/PoshWeb/)"
    "* [MarkX](https://github.com/PoshWeb/MarkX)"
) -join [Environment]::Newline | 
    # convert it from markdown
    ConvertFrom-Markdown |
    # turn it into xml
    Foreach-Object {
        "<article>$($_.Html)</article>" -as [xml]
    } |
    # pipe it to Select-Xml, picking out any `<a>` elements
    Select-Xml //a |
    Foreach-Object { 
        $_.Node.Href
    }

This is still the tip of the iceberg.

Turning Markdown into XML lets us query and manipulate Markdown in all sorts of interesting ways.

What can you do with Markdown and PowerShell? Almost anything.

Mark My Words

  • Markdown is a simple rich text format.
  • PowerShell is pretty perfect for making Markdown.
  • XPath is excellent at extracting information from Markdown.

You can do a lot of cool things when you mix Markdown with PowerShell.

What do you want to try?

u/StartAutomating — 3 months ago

Static Sites are Simple (with PowerShell)

I've been doing WebDev since the dawn of the internet, and I've been doing PowerShell for almost 20 years now. I want to share with you something that I've realized over the years:

Static Sites Are Simple

Static Websites are just a bunch of files. You can make static sites with anything that can make files.

Static Sites are Simple.

Let me show you how:

Static Sites with PowerShell

PowerShell is pretty great at making files.

Most static site files are text: .css, .js.,.html,.svg are all readable and writeable text.

Want to write a website in PowerShell?

Just write a series of strings.

I like this naming convention:

# *.html.ps1 > *.html

We can build a site like this:

# Get all *.html.ps1 files beneath the current directory
Get-ChildItem -Filter *.html.ps1 -Recurse -File | 
   Foreach-Object {
      # Run the file 
      & $_ > $(
          # and redirect the output to the renamed `.html`
          $_.Fullname -replace '\.html\.ps1$','.html'
      )
   }

If we wanted to provide consistent formatting for all *.html.ps1 files, we can do so with a layout.

Just write a freeform script for layout.

function layout {
   
   # Output any common layout.

   # We are outputting a series of strings.

   # When we redirect output, each string will go on it's own line.
   
   # We can use any simple PowerShell string techniques to change content
   
   '<html>' # * Single quoted string (no substitutions)
   "<head>" # * Double quoted string (`$var` and `$(expression)` supported) 
       # * Multiline double quoted strings (with subexpressions)
       "<title>$(
        if ($title) { 
            [Web.HttpUtility]::HTMLEncode($title)
        } else { 'My Website' }
        )
        </title>"
        # * Conditionals output, using if
        if ($Header) {
             "$Header" # * Stringification of variables
        }
        # * Singly quoted here-strings (mulit-line no substitution)
        @'
<style>
body {max-width: 100vw;height: 100vh;}
</style>
'@
        # * Doubly-quoted here-strings 
        @"
$(
# * Subexpressions with conditionals and iteration
if ($css) {$css})
"@

   "</head>"
   "<body>"
    # * `$input` allows us fast, one-time enumeration of a pipeline
    # * `@()` allows us to collect that into a new list
    $allInput = @($input)
    
    # * String operators (`-join`, `-like`, `-match`,`-replace`, `-split`).
    $allInput -join [Environment]::Newline
    "</body></html>"
}

Now, we can build it with:

# Get all *.html.ps1 files beneath the current directory
Get-ChildItem -Filter *.html.ps1 -Recurse -File | 
   Foreach-Object {
      # Run the file, pipe to our layout 
      & $_ | layout > $(
          # and redirect the output to the renamed `.html`
          $_.Fullname -replace '\.html\.ps1$','.html'
      )
   }

If we want to handle multiple file types, a switch statement does a nice job. We can build the site any way we want. This is just one example of how.

Most templating languages can't talk to too much. By using PowerShell to make static sites, we open up a wide world of possibilities with a small amount of understanding.

Static Sites Are Simple

They're mainly just strings.

PowerShell plays with strings quite well 😉.

Hope this Helps / AMA

reddit.com
u/StartAutomating — 3 months ago