Tahir Hassan's Blog

My Technical Notes

Friday, 7 April 2017

PowerShell: Performance of different approaches to `Add-Type` guard


function Get-Performance 
{
    [CmdletBinding()]
    param( [scriptblock]$sb )

    $name = $sb.ToString().Trim();
    
    Write-Verbose "Starting $name..."
    $s = [System.Diagnostics.Stopwatch]::StartNew(); 
    (1..250000) | % $sb; 
    $s.Stop(); 
    $elapsed = $s.Elapsed;
    
    Write-Verbose $elapsed;
    Write-Verbose "...Finished $name";
    
    [pscustomobject]@{
        Name = $name;
        Elapsed = $elapsed
    };
}

# the control function should have some code in there to ensure that we are measuring the different approaches.
function Lock-WorkStation_Control {
    $true | Out-Null
}

function Lock-WorkStation_TypeVariable {
    if ($Script:LockWorkStationTypeVariable -eq $null) {
        $namespace = 'Win32FunctionsTypeVariable'
        $name = 'Win32LockWorkStation'
        
        $signature = @'
[DllImport("user32.dll", SetLastError = true)]
public static extern bool LockWorkStation();
'@
        $Script:LockWorkStationTypeVariable = Add-Type -Namespace $namespace -Name $name -MemberDefinition $signature -PassThru
    }
    
    $Script:LockWorkStationTypeVariable::LockWorkStation() | Out-Null
}

function Lock-WorkStation_Exception {
    
    try {
        [Win32FunctionsException.Win32LockWorkStation] | Out-Null
    } catch {
        $namespace = 'Win32FunctionsException'
        $name = 'Win32LockWorkStation'
        
        $signature = @'
[DllImport("user32.dll", SetLastError = true)]
public static extern bool LockWorkStation();
'@
        Add-Type -Namespace $namespace -Name $name -MemberDefinition $signature
    }
    
    [Win32FunctionsException.Win32LockWorkStation]::LockWorkStation() | Out-Null
}

function Lock-WorkStation_Unconditional {
    $namespace = 'Win32FunctionsUnconditional'
    $name = 'Win32LockWorkStation'
    
    $signature = @'
[DllImport("user32.dll", SetLastError = true)]
public static extern bool LockWorkStation();
'@
        
    $LockWorkStationUnconditional = Add-Type -Namespace $namespace -Name $name -MemberDefinition $signature -PassThru      
    
    $LockWorkStationUnconditional::LockWorkStation() | Out-Null
}

function Lock-WorkStation_Mathias {
    $namespace = 'Win32FunctionsMathias'
    $name = 'Win32LockWorkStation'
        
    if(-not ($LockWorkStationMathias = "$namespace.$name" -as [type])){
        $signature = @'
[DllImport("user32.dll", SetLastError = true)]
public static extern bool LockWorkStation();
'@

        $LockWorkStationMathias = Add-Type -Namespace $namespace -Name $name -MemberDefinition $signature -PassThru
    }
    
    $LockWorkStationMathias::LockWorkStation() | Out-Null
}

function Lock-WorkStation_Boolean_VariableAccess {
    if (!$Script:LockWorkStationDefinedA) {
        $namespace = 'Win32FunctionsBooleanVAccess'
        $name = 'Win32LockWorkStation'
        
        $signature = @'
[DllImport("user32.dll", SetLastError = true)]
public static extern bool LockWorkStation();
'@
        $Script:LockWorkStationVar = Add-Type -Namespace $namespace -Name $name -MemberDefinition $signature -PassThru
        $Script:LockWorkStationDefinedA = $true;
    }
    
    $Script:LockWorkStationVar::LockWorkStation() | Out-Null
}

function Lock-WorkStation_Boolean_TypeAccess {
    if (!$Script:LockWorkStationDefinedB) {
        $namespace = 'Win32FunctionsTAccess'
        $name = 'Win32LockWorkStation'
        
        $signature = @'
[DllImport("user32.dll", SetLastError = true)]
public static extern bool LockWorkStation();
'@
        Add-Type -Namespace $namespace -Name $name -MemberDefinition $signature 
        $Script:LockWorkStationDefinedB = $true;
    }
    
    [Win32FunctionsTAccess.Win32LockWorkStation]::LockWorkStation() | Out-Null
}

$SBs = @(
    { Lock-WorkStation_Control },
    { Lock-WorkStation_Exception },
    # { Lock-WorkStation_Unconditional },
    { Lock-WorkStation_Mathias },
    { Lock-WorkStation_Boolean_VariableAccess },
    { Lock-WorkStation_Boolean_TypeAccess },
    { Lock-WorkStation_TypeVariable }
);

$performances = $SBs | % { try { Get-Performance $_ -Verbose } catch { } }

$performances | sort Elapsed -Descending | Format-Table
Running it once, I found:

Name                                    Elapsed
----                                    -------
Lock-WorkStation_Unconditional          00:01:34.9005335
Lock-WorkStation_Exception              00:00:47.0459598
Lock-WorkStation_Mathias                00:00:38.2524806
Lock-WorkStation_Boolean_TypeAccess     00:00:37.1698860
Lock-WorkStation_Boolean_VariableAccess 00:00:36.9747986
Lock-WorkStation_TypeVariable           00:00:36.8496781
Lock-WorkStation_Control                00:00:24.5562487

Monday, 3 April 2017

Reducing Bass on Windows 10 with Equalizer APO

If, like me, you are using headphones plugged directly into your PC/Laptop, you will have no hardware bass controls. Windows 10 also does not include sound equalizer software. Sometimes, sound drivers can include such software - a good example being Realtek - but my sound driver unfortunately did not. Instead, I am using Equalizer APO, a free, open-source equalizer. Here are some instructions on how to set it up to reduce bass levels.

Download/Install Equalizer APO   Download Equalizer APO from SourceForge and install it. When it asks for which devices you want to install APO for, I selected only the current playback device, but it shouldn't matter if you select all sound devices. After installation, you will have to restart your computer.

Download/Install Peace UI for Equalizer APO   Download Peace UI from SourceForge and install it. Accept all the default installation options. It should identify that Equalizer APO is already installed.

Opening Peace   You can open Peace from the Start menu. Select the Full interface, as it will allow you to save presets.

Creating a `Bass Reduce` preset   A bass reduction preset can be made by simply inverting the gain values of the `Bass Boost` option. First select the `Bass Boost` preset from the list in the bottom left of the screen. The app will re-open with the preset values applied. For each of the `Gain Values`, invert the value (multiply it by `-1`). For example, if the value was `10.5`, make it `-10.5`, and if it was `-3`, make it `3`. Click `Save` and enter the name `Bass Reduce`.

Reselecting `Bass Reduce` Option   Once the `Bass Reduce` preset is saved, it can be simply selected from the list in the bottom left of the screen. The app will re-open to apply the preset options.

Sunday, 2 April 2017

SQL Server 2016 Services on Windows Professional

Installing SQL Server 2016 on Windows 10 Professional will result in a prethora of services being installed, as can be seen in the installer window:

Many of these services have their start type set to `Automatic`, which means that they will startup with Windows. However, you might not need SQL Server running in the background all the time.

Below, I am using PowerShell to change the startup configuration of these services.

Listing all SQL Server Services   The following code will get all services whose name starts with "SQL":


Get-Service | ? { $_.DisplayName -match '^SQL' }

which results in the following data (I've used `Format-Table -AutoSize` to make it prettier):


Status  Name                                     DisplayName                                      
------  ----                                     -----------                                      
Running MsDtsServer130                           SQL Server Integration Services 13.0             
Running MSOLAP$SQL2016                           SQL Server Analysis Services (SQL2016)           
Running MSSQL$SQL2016                            SQL Server (SQL2016)                             
Running MSSQLFDLauncher$SQL2016                  SQL Full-text Filter Daemon Launcher (SQL2016)   
Running MSSQLLaunchpad$SQL2016                   SQL Server Launchpad (SQL2016)                   
Running ReportServer$SQL2016                     SQL Server Reporting Services (SQL2016)          
Stopped SQL Server Distributed Replay Client     SQL Server Distributed Replay Client             
Stopped SQL Server Distributed Replay Controller SQL Server Distributed Replay Controller         
Stopped SQLAgent$SQL2016                         SQL Server Agent (SQL2016)                       
Running SQLBrowser                               SQL Server Browser                               
Stopped SQLPBDMS$SQL2016                         SQL Server PolyBase Data Movement (SQL2016)      
Stopped SQLPBENGINE$SQL2016                      SQL Server PolyBase Engine (SQL2016)             
Running SQLTELEMETRY$SQL2016                     SQL Server CEIP service (SQL2016)                
Running SQLWriter                                SQL Server VSS Writer                            
Running SSASTELEMETRY$SQL2016                    SQL Server Analysis Services CEIP (SQL2016)      
Running SSISTELEMETRY130                         SQL Server Integration Services CEIP service 13.0

Listing SQL Services that start automatically   Here is how to list SQL services that start automatically with Windows:


Get-Service | 
    ? { $_.DisplayName -match '^SQL' -and $_.StartType -eq 'Automatic' } | 
    select Name, DisplayName, Status, StartType

which returns:


Name                   DisplayName                                        Status StartType
----                   -----------                                        ------ ---------
MsDtsServer130         SQL Server Integration Services 13.0              Running Automatic
MSOLAP$SQL2016         SQL Server Analysis Services (SQL2016)            Running Automatic
MSSQL$SQL2016          SQL Server (SQL2016)                              Running Automatic
MSSQLLaunchpad$SQL2016 SQL Server Launchpad (SQL2016)                    Running Automatic
ReportServer$SQL2016   SQL Server Reporting Services (SQL2016)           Running Automatic
SQLBrowser             SQL Server Browser                                Running Automatic
SQLPBDMS$SQL2016       SQL Server PolyBase Data Movement (SQL2016)       Running Automatic
SQLPBENGINE$SQL2016    SQL Server PolyBase Engine (SQL2016)              Stopped Automatic
SQLTELEMETRY$SQL2016   SQL Server CEIP service (SQL2016)                 Running Automatic
SQLWriter              SQL Server VSS Writer                             Running Automatic
SSASTELEMETRY$SQL2016  SQL Server Analysis Services CEIP (SQL2016)       Running Automatic
SSISTELEMETRY130       SQL Server Integration Services CEIP service 13.0 Running Automatic

Setting Automatically Starting SQL Services to Manual Startup   Before changing the startup types of any SQL services, you should first take a backup of how these services are configured to start up. Do:


Get-Service | 
    ? { $_.DisplayName -match '^SQL' } | 
    select Name, DisplayName, Status, StartType | 
    Export-Csv "SqlServerServices.csv"

As with any other backup procedure, keep this CSV file safe, as you may need to restore the default startup configuration of these services.

To change all the above services to not start up with Windows, i.e. make it's startup `Manual`, run the following code (as Administrator):


Get-Service | 
    ? { $_.DisplayName -match '^SQL' -and $_.StartType -eq 'Automatic' } | 
    Set-Service -StartupType Manual

Restoring Automatically Starting SQL Services   Read the CSV file, then use `Set-Service` to set the startup type back to `Automatic` (only for those services whose startup type was `Automatic` to begin with). Assuming that `SqlServerServices.csv` is in the working directory, do:


Import-Csv .\SqlServerServices.csv | 
    ? { $_.StartType -eq 'Automatic' } |
    % Name | 
    Get-Service | 
    Set-Service -StartupType Automatic

Tuesday, 28 March 2017

Using Gimp for precise cropping

Gimp is an excellent tool for precisely cropping an image. Once Gimp is loaded, you an open an image by either using File → Open (Ctrl-o), or using invoking Gimp from the command line passing in the path of the file. Because Gimp takes a long time to load, it is not worth having multiple instances of it open; rather, have one instance open, and open the files you wish to edit in this one instance.

Once you have opened the image in Gimp, select the "Rectangle" tool. Check "Fixed"; in the drop-down next to it, select "Aspect ratio". In the textbox below it, type in the aspect ratio in `width:height` format, for example, `4:3` or `16:9`:

Draw around the region you wish to capture. Once you are done, click on Image → "Crop to Selection". Save this new image.

Monday, 27 March 2017

Converting ORG to PDF with custom CSS styling

OrgMode is a very simple and easy to use plugin for Emacs that allows you to organize yourself using text only.

One drawback of it is that Emacs/OrgMode has a high learning curve: too many keyboard shortcuts to remember, requires use of lisp to customise output etc.

However, its main strength is that OrgMode files are purely text - and text is very easy to process.

Emacs OrgMode already has PDF output capability (via LaTeX). However, to style its output, you have to use custom Lisp functions containing LaTeX code. As I am unfamiliar with both LaTeX and Lisp, I wanted to use familiar technologies, such as CSS, to style the content, and use Emacs purely for editing the OrgMode files.

Installing Pandoc   Pandoc needs to be installed first.

Installing wkhtmltopdf   wkhtmltopdf also needs to be installed because that is what actually does the HTML to PDF conversion. You also have to add `C:\Program Files\wkhtmltopdf\bin` to the `PATH` variable so it is visible to Pandoc.

Including CSS   Below is a sample css file `sample.css` which makes `.done` tasks green:


.done {
    color: green;
}

Place it in the same folder as the `.org` folder for the commands below, which reference its path, to work.

OrgMode file   The commands will be run against the following OrgMode file, `doc.org`:


* DONE this is a done task
* DONE this is a second done task.

Pandoc Command   Execute the following command to create `doc.pdf` from `doc.org`:


pandoc -t html5 --css sample.css doc.org -o doc.pdf

The `-t html5` in the command ensures that it uses `wkhtmltopdf` to create the PDF document instead of LaTeX.

Sources

  • StackOverflow.com An answer explaining the user of pandoc to convert OrgMode to PDF

Saturday, 25 March 2017

Generating Random Strings in PowerShell

As part of unit testing my PowerShell functions, I have noticed a small potential issue with having data hard-coded into unit tests. Such tests can be made to pass by hard-coding the answer in the tested function. For instance we could have a unit test for a function called `Concat-String` that takes two `string` arguments and concatenates them together:


$result = Concat-String "Tahir" "Riyadh"
Assert-AreEqual "TahirRiyadh" $result

Such a test could easily be passed by hard-coding a return value:


Function Concat-String([string]$First, [string]$Second) {
    "TahirRiyadh"
}

(Of course, a unit-testing purist would say that you need multiple tests to prevent the possibility of hard-coded values, but that is besides the point - I only want to write one unit test, not many versions of the same test with different data).

Instead, what I am now using to generate test data is the following function, `New-RandomString`:


Add-Type -AssemblyName System.Security

Function New-RandomString {
    param([Parameter()][int]$Size=10)

    $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_".ToCharArray();
    
    $bytes = & {
        $data = [byte[]]::new($Size)
        $crypto = [System.Security.Cryptography.RNGCryptoServiceProvider]::new();
        $crypto.GetBytes($data)
        $data
    }

    $str = [char[]]($bytes | % { $_ % $chars.Length } | % { $chars[$_] })

    [string]::new($str)
}

Instead of hard-coding test data in the unit test, `New-RandomString` can be used to give appropriate test values:


$firstVal = New-RandomString 15
$secondVal = New-RandomString 20
$result = Concat-String $firstVal $secondVal
$expected = $firstVal + $secondVal
Assert-AreEqual $expected $result

Such a test cannot be passed by hard-coding a correct return value. It can also be run multiple times to ensure that it passes i all cases.

Sources

Wednesday, 22 March 2017

Using DebugView for Debugging AutoHotkey Scripts

DebugView is an application made by SysInternals that can be used to view debug messages produced by applications.

To create a debug message using AutoHotkey, use the `OutputDebug` command:


OutputDebug, Hello World

Because many other applications also produce debug messages, you want to be able to show only those messages produced by your AutoHotkey script. For this, you can wrap the above command in a function that prepends `[AHK] ` to messages to make them searchable:


DebugMsg(Msg) {
    OutputDebug, [AHK] %Msg%
}

This function is called by passing in a string argument, as below demonstrates. The result is that `[AHK] Hello World` will appear in DebugView's message list.


DebugMsg("Hello World")

Configuring DebugView

Filter to only include [AHK] messages

To configure DebugView to only show messages from AutoHotkey, first show the "DebugView Filter" dialog, by either doing `Ctrl-L` or Edit → Filter/Highlight. In this dialog, in the `Include` field, add `[AHK]*` and press OK. This will not filter existing messages in the list, only new ones.

Highlighting messages by color
To highlight messages containing some text by color, first select a Filter from the list, and then in the colored textbox below it, enter some text to highlight:

Strangely enough, in contrast to the filtering behavior only affecting newly captured messages, the highlighting affects existing messages too.

Other...

If DebugView is not capturing messages (with filter set as `*`), then you may have to run it as administrator.