Skip to main content

Building Powershell GUIs that won’t freeze when you use them

A few days ago I wrote about how important I think it is that you build a GUI for your scripts if they are to be used by anoyone else.

If you have ever built a GUI using Powershell however, you know there is a problem with it that I didn’t mention, that is, whenever your script is actually doing anything the GUI will freeze and windows will tell you it isn’t responding. This is not good as most users will then try to close it and try again. We don’t want that.

The reason this happens is because you are running your form (GUI) and logic in the same thread, something you should never do, problem is, Powershells support for multithreading is limited at best.

In the last post I also talked about my module EasyGUI and mentioned that there are features I wouldnt go in to in that post, one of those features is an easy way to use threads.

There are two ways to use threads in EasyGUI, I go through both of them below as well as a tip on how I prefer to use them.

Method 1.

Passing in a scriptblock to the thread, here I am creating it on the fly but you could also store the scriptblock in a variable and pass that to the function instead

New-Thread {
    0..60|Foreach-Object{
        $SYNC.myLabel.Text = $_
    }
}

Method 2.

You could also write the code for the thread in a separate script file and then pass that to the New-Thread function like this

New-Thread "D:\Program\Powershell\myThread.ps1"

One thing to be aware of is that the threads will start running emediatly when you create them. Often you might not want that. What I usually do is create the threads inside a scriptblock that I store in a variable, then when I need the tread to run I run that scriptblock. That would look something like this.

$myThread = {
    New-Thread {
        0...60|Foreach-Object{
            $SYNC.myLabel.Text = $_
        }
    }
}

#Now it's time to run the thread, simply type the & sign and then the variable containing the scriptblock
&$myThread

$SYNC ? Where did that come from?

Noticed that did you?

This is a variable that’s automatically created when you initialize the EasyGUI module. It’s a hashtable that’s synced between all threads so you can communicate between them. In the examples you see that I am using $SYNC.myLabel. This is created in the GUI like this.

$SYNC.myLabel = New-Label @{
    Location = "15, 15"
}

or like this

$myLabel = New-Label @{
    Location = "15, 15"
}
$SYNC.myLabel = $myLabel

If you want to see a complete example of a GUI script with threading implemented there is an example availiable in the Easy GUI repository.

Share

Booksonic Bridge Preview

As part of communicating more what happens with the Booksonic project I thought it might be a good idea to show what the Bridge will look like.
Important to know is that most parts of the bridge currently doesn’t have an interface but as they are created I’ll add them to this post.

If you have any suggestions or ideas for improvements to the design let me know in a comment here, at reddit.com/r/booksonic or at [email protected]

Share

Writing Powershell GUIs the easy way

One of the most important tools for any IT professional or service desk technician that works in a Windows environment is without a doubt Powershell. Not only is it extremely powerful but it is also very fun to work with if you ask me.

One of the great features of Powershell that I feel doesn’t get enough love from the Powershell community is the ability to write scripts with graphical user interfaces. Yeah, yeah, I know, you want to be able to do a quick change in the script and then rerun it inside ISE and anyone that doesn’t know how to use a console shouldn’t be in IT anyway, right? Wrong!

It is my firm belief that a clear and easy to use interface is just as important as the logic itself when you write a script that is going to be used by someone else. For most people, that means the interface needs to be graphical. I base this belief on having worked in an IT department at a large company for several years as well as maintained the Booksonic project since the start in late 2015. If you want to change my mind, feel free to try and do so in the comments below.

Now with that out of the way I will stop trying to convince you about writing user interfaces for your scripts, the fact that you are reading this post probably means that you are already doing it or thinking about doing it. Instead I am going to focus on how to do it in a way so the code is easy to read and maintain even once you start writing more complex UIs.

When I first started writing powershell I noticed that most examples of people writing powershell UIs get really messy real fast.

A typical example you may find online looks something like this

Add-Type -AssemblyName System.Windows.Forms
Add-Type -Name Window -Namespace Console -MemberDefinition '
        [DllImport("Kernel32.dll")]
        public static extern IntPtr GetConsoleWindow();     
        [DllImport("user32.dll")]
        public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);

[Console.Window]::ShowWindow([Console.Window]::GetConsoleWindow(), 0)       '

$font = New-Object System.Drawing.Font("Times New Roman",24,[System.Drawing.FontStyle]::Bold)

$label = New-Object System.Windows.Forms.Label
$label.Cursor = [System.Windows.Forms.Cursors]::Hand
$label.Text = "Click me to open popeen.com" 
$label.BackColor = "Transparent"
$label.ForeColor = "Blue" 
$label.AutoSize = $true
$label.Location.X = 15
$label.Location.Y = 15
$label.Add_Click = { Start-Process -FilePath iexplore  -ArgumentList "https://popeen.com" }

$form = New-Object system.Windows.Forms.Form
$form.Width = 600
$form.Height = 200
$form.Text = "Example form"
$form.Font = $font
$form.Controls.Add($label)
$form.ShowDialog() 

Now functionally speaking there is nothing wrong with that script and in fact it may not look that bad either but remember, all we have done is created a window and added one label to it. Imagine how cluttered this would become once you started doing some more advanced UIs. A better way to write the above that I have actually never seen anyone use other than me and some of my colleages would be using -Properties like this.

Add-Type -AssemblyName System.Windows.Forms
Add-Type -Name Window -Namespace Console -MemberDefinition '
        [DllImport("Kernel32.dll")]
        public static extern IntPtr GetConsoleWindow();     
        [DllImport("user32.dll")]
        public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);

[Console.Window]::ShowWindow([Console.Window]::GetConsoleWindow(), 0)

$label = New-Object System.Windows.Forms.Label -Properties @{
    Cursor = [System.Windows.Forms.Cursors]::Hand
    Text = "Click me to open popeen.com" 
    BackColor = "Transparent"
    ForeColor = "Blue"
    AutoSize = $true
    Location = "15, 15"
    Add_Click = { Start-Process -FilePath iexplore  -ArgumentList "https://popeen.com" }
}

$form = New-Object system.Windows.Forms.Form -Properties @{
    Size = "600, 200"
    Text = "Example form"
    Font = New-Object System.Drawing.Font("Times New Roman", 24, [System.Drawing.FontStyle]::Bold)
}
$form.Controls.Add($label)
$form.ShowDialog() 

While this takes up about the same space it looks much better right? This way a quick glance at the code gives you a much better understanding of what it is you are looking at.

If you choose to stop reading here and start writing your UIs in this way I beleive you will have a much easier time maintaining that script then you had before. However, I have taken it a step further in my scripts.

When I write a script I use a specific framework that makes the code even quicker to read and also includes some other nice features that I won’t go in to in this post but might make future posts about. One of the most important parts of this framework is a module I call EasyGUI and that you can download at https://gitlab.com/Popeen/EasyGUI

This module builds on the second example above and simplifies it even more. It also includes some other cool stuff that I also won’t go in to here but again, might make another post about. If we decided to use EasyGUI to write the example above it would look like this

Import-Module EasyGUI

Initialize-EasyGUI
Hide-Console

$form = New-Form @{
    Size = "600, 200"
    Text = "Example form"
    Font = New-Font -Font "Times New Roman" -Size 24 -Style $FONTSTYLE.Bold
}

$label = New-Label @{
    Cursor = $CURSOR.Hand
    Text = "Click me to open popeen.com" 
    BackColor = "Transparent"
    ForeColor = "Blue"
    AutoSize = $true
    Location = "15, 15"
    Add_Click = { Start-Process -FilePath iexplore  -ArgumentList "https://popeen.com" }
}

$form.Controls.Add($label)

Show-Form $form
Stop-Console 

I dont know about you but I find that to be much more readable then the first example we looked at, especially when you are working with a lot more then just a label.

It should be noted that not every forms object is in EasyGUI yet as I add them when I need them but adding them yourself should be very easy even for someone new to Powershell and if not just post a comment below and let me know what object you would like added.

I would love to hear your thoughts on this, is it something you would ever use or do you think I’m just talking rubbish when I say that UIs are this important or that a simple module like this can make it a lot more quick, fun and easy to code and maintain UIs.

Share

Booksonic FAQ

Last updated: July 30, 2019

Here I try to answer the most common questions I get regarding Booksonic. Depending on what the problem you are experiencing is the answers may not always apply to you. If the answer doesn’t help you or you have a problem that is not mentioned here do the following

  • Restart the app
  • Do the thing that fails
  • Go to the main screen of the app
  • Tap the dots at the top right
  • Tap “Send Log”
  • Enter a description of the problem and then send it to me, I will get back to you as soon as I can.

Another great resource for getting help with Booksonic is to go to the Booksonic Reddit and ask the question there. You can find it at https://reddit.com/r/booksonic


The app is just showing white and then crashes when I open it

This can sometimes happen if you dont have a profilepicture set on the server.
Go to the server in the browser and then Settings -> Personal then select a Profile/Personal image and click save.
There will be a fix for this in a later version of the app.


I can’t connect to the server outside of my network

Make sure that you have portforwarded the port you are using in your router. The default port is 4040. Also make sure that you are specifying the port when you try to go to the server. It should look something like this http(s)://example.com:4040


I can connect to the server in the browser but not in the app

Make sure that you enter http:// or https:// at the beginning of the adress and :4040 at the end. If you are using docker or tomcat, make sure to also include /booksonic at the end.


The app keeps crashing when I do X, please help

To be able to help you I need to see the log file. See the list at the top of this post for instructions on how to send me one.


The server sais “Folder not found” when I try to use a network location

This is due to how windows handles permissions. I have posted instructions on how to fix it at https://popeen.com/2016/05/27/using-a-network-shared-folder-in-booksonic/


Casting to Chromecast fails

This will happen if you use a self signed certificate as Chromecast does not support those. There is a workaround for it in the app that you can enable. Go to Settings -> Playback and enable Use device as a proxy, this should fix your problem.


What is the difference between cache and permanent cache?

Files that are cached by Booksonic will normally be automatically removed to make space for newer files once you have reached your cache limit. If you download them using permanent cache they will never be deleted automatically.


When I try to reset my password it says reCaptcha v1 is shut down, can I still reset it?

To reset your password do the following

  • Log on to the machine hosting the server
  • Stop the server, verify that you can’t reach it in the browser
  • use notepad to open c:\booksonic\db\booksonic.script, make sure that you set notepad to Any file or you won’t see it.
  • Press CTRL + F and search for INSERT INTO USER VALUES
  • After your username you will see enc:lots of numbers
  • Replace all the numbers that are before the ‘ sign with 626f6f6b736f6e6963
  • Save the file and close notepad
  • Start the server

You can now sign in with the password booksonic, make sure the first thing you do is change your password.

If you are running it on a Linux machine the file is probably located at /var/booksonic/db/booksonic.script


I am getting the message <UNKNOWN SSID> when I try to enter SSID in server settings

There was a change in a recent version of Android that requires apps to have location permission before they can see the name of the SSID you are connected to. In the future Booksonic will ask for this permission but for now you have to give it manually from your phone’s settings menu.

The settings menu can look a bit different depending on your phone but it should be something like this, settings -> apps -> Booksonic -> permissions. Then enable the location permission. Go back one step and force close Booksonic.

Now reopen Booksonic and you should be able to get the SSID.


Do you plan to release an iOS version of Booksonic or would you consider it?

No I don’t plan on creating an iOS version and I will most likely never change my mind regarding this. However, I am working on a new server that will have an HTML5 player that should work just as good on iOS as on Android.

In the meantime you can use any iOS app that was created for Subsonic to connect to your Booksonic server. Or you can use webapps like Jamstash or Aurial. These will work just fine for playback but you won’t get any book descriptions or narrator info.


Is Booksonic open-source?

Yes! Booksonic is indeed open source. The server is a fork of the Subsonic server and the Android app is a fork of the DSub app.
You can find the source code for Booksonic at https://github.com/popeen


I have a great idea for a feature, how do I contact you about it?

You can send it to my email at [email protected], open an issue at https://github.com/popeen/popeens-dsub or post it on https://reddit.com/r/booksonic
While I might sometimes be a bit inactive in posting on GitHub or Reddit I always keep up to date with what is posted.

If you know how to code and want to implement the feature yourself, go for it. Once you are done, send me a pull request on GitHub and as long as the feature doesn’t break compatibility with regular Subsonic servers I will most likely accept it.

Share