Topshelf install, PowerShell and Get-Credentials

In the project I’m currently working at we use PowerShell script for configuration and build execution.

This means that if you get a new laptop, or a new member joins the team, or even when you need to change your Windows password, you just need to run the script again and it will set up everything in the correct locations & with the correct credentials.

The credentials were a problem though.

When installing a Topshelf service with the --interactive parameter (we need to install under the current user, not System) it will prompt you for your credentials for each service you want to install. For one, it’s fine, for 2, it’s already boring, for 3, … You get the point.

We initially used the following command line to install the services:

. $pathToServiceExe --install --interactive --autostart

To fix this we will give the $pathToServiceExe the username and password ourselves with the -username and -password. We should also omit the --interactive.

First gotcha here: When reading the documentation, it says one must specify the commands in this format:

. $pathToServiceExe --install --autostart -username:username -password:password

However, this is not the case. You mustn’t separate the command line argument and the value with a :.

Now, we don’t want to hardcode the username & password file in the setup script.

So let’s get the credentials of the current user:

$credentialsOfCurrentUser = Get-Credential -Message "Please enter your username & password for the service installs"

Next up we should extract the username & password of the $credentialsOfCurrentUser variable, as we need it in clear-text (potential security risk!).

One can do this in 2 ways, either by getting the NetworkCredential from the PSCredential with GetNetworkCredential():

$networkCredentials = $credentialsOfCurrentUser.GetNetworkCredential();
$username = ("{0}\{1}") -f $networkCredentials.Domain, $networkCredentials.UserName # change this if you want the user@domain syntax, it will then have an empty Domain and everything will be in UserName. 
$password = $networkCredentials.Password

Notice the $username caveat.

Or, by not converting it to a NetworkCredential:

# notice the UserName contains the Domain AND the UserName, no need to extract it separately
$username = $credentialsOfCurrentUser.UserName

# little more for the password
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($credentialsOfCurrentUser.Password)
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

Notice the extra code to retrieve the $password in plain-text.

I would recommend combining both, using the NetworkCredential for the $password, but the regular PSCredential for the $username as then you’re not dependent on how your user enters his username.

So the best version is:

$credentialsOfCurrentUser = Get-Credential -Message "Please enter your username & password for the service installs" 
$networkCredentials = $credentialsOfCurrentUser.GetNetworkCredential();
$username = $credentialsOfCurrentUser.UserName
$password = $networkCredentials.Password

Now that we have those variables we can pass them on to the install of the Topshelf exe:

. $pathToServiceExe install -username `"$username`" -password `"$password`" --autostart

Notice the backticks (`) to ensure the double quotes are escaped.

In this way you can install all your services and only prompt your user for his credentials once!

When using an enum in PowerShell, use the member’s name, not the member’s value

Consider the following enum in C#:

enum State
{
    Started,
    Stopped,
    Unknown
}

Note that I have not added an explicit value for the enum members. They will be generated by the compiler. As stated in the C# spec:

… its associated value is set implicitly, as follows:

  • If the enum member is the first enum member declared in the enum type, its associated value is zero.
  • Otherwise, the associated value of the enum member is obtained by increasing the associated value of the textually preceding enum member by one. This increased value must be within the range of values that can be represented by the underlying type, otherwise a compile-time error occurs.

Found at http://www.microsoft.com/en-us/download/details.aspx?id=7029, page 400-401 (I can’t find the version for 4.5 though…).

Now what are the consequences of this? Consider the following piece of PowerShell:

$result = $serviceController.GetServiceStatus()
if($result -eq 1)
{
    MyLib.StartService()
}

This will work, because PowerShell implicitly converts the int to the actual enum member.

However since we are assuming the value can go wrong. In the next version you add extra values, say for example to represent a starting/stopping service:

enum State
{
    Starting,
    Started,
    Stopping,
    Stopped,
    Unknown
}

Since now all the values are shifted when you run your PowerShell again you start the service when it’s already started 😉 .

Solution?

First of all (as a consumer), use the enum’s member name instead of its value:

$result = $serviceController.GetServiceStatus()
if($result -eq [MyLib.State]::Stopped)
{
    MyLib.StartService()
}

This will ensure that you get the value for Started, not for anything else.

As a developer of a library you should ensure that you never mess up the order of an enum, by adding new values as last, or (prefered) set the value yourself:

enum State
{
    Started = 0,
    Stopped = 1,
    Unknown = 2,
}

Becomes:

enum State
{
    Starting = 3,
    Started = 0,
    Stopping = 4,
    Stopped = 1,
    Unknown = 2,
}

And now you can also perfectly reorder them so the numbers are sequential:

enum State
{
    Started = 0,
    Stopped = 1,
    Unknown = 2,
    Starting = 3,
    Stopping = 4,
}

Hope you have a good one,

-Kristof

ServerConnection and Login failed for user. Reason: Attempting to use an NT account name with SQL Server Authentication

Today I had to work with the ServerConnection class.

This class provides a method to specify the connection to the Server class.

So usage would be like this:

# http://sqlblog.com/blogs/allen_white/archive/2008/04/28/create-database-from-powershell.aspx
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO')  
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.ConnectionInfo')  

$sqlServer = "server"
$username = "username"
$password= "password"

$serverConnection = new-object Microsoft.SqlServer.Management.Common.ServerConnection($sqlServer, $username, $password)

$server = new-object Microsoft.SqlServer.Management.Smo.Server($serverConnection)

Write-Host ("SQL Version: {0}" -f $server.Information.Version)

$server.databases | % { Write-Host $_.Name }

Now this works for SQL accounts, but not for domain accounts.

My username was in the form of DOMAIN\username, but that failed.

Checking the SQL Server log, it yielded this:

Login failed for user. Reason: Attempting to use an NT account name with SQL Server Authentication
Login failed for user. Reason: Attempting to use an NT account name with SQL Server Authentication

So to use the domain account with this object you need to create the $serverConnection like this AND you need to specify your username in the form of: username@domain.local (FQN). Entering DOMAIN\username doesn’t seem to work.

$sqlServer = "server"
$username = "username@domain.local"
$password= "password"

$serverConnection = new-object Microsoft.SqlServer.Management.Common.ServerConnection($sqlServer)

$serverConnection.ConnectAsUser = $true
$serverConnection.ConnectAsUsername = $username
$serverConnection.ConnectAsUserPassword = $password

You need to use the ConnectAsUsername and ConnectAsUserPassword to use domain accounts and set the ConnectAsUser property to true.

When I connect with those options I get the access I need.

You can verify it by executing the following query:

$conn.ExecuteScalar("SELECT SUSER_NAME() as [foo]")

Which nicely yields:

username@domain.local

Have a good one,

-Kristof

Powershell, Where-Object and capturing the output

Again, one that thing I couldn’t find because there is just no documentation on Powershell.

Try this:

$foo = SomethingThatResturnsAList

$foo = $foo | Where-Object { $_.Name -like "*something*" }

PassOnFooToSomethingElse -List $foo

Awesome, every sane developer would think: right, that would work. Except in Powershell.

It appears that $foo on line 5 is $null

WHAT?

Okay, let’s surround it with braces, maybe that’ll solve stuff:

$foo = ( $foo | Where-Object { $_.Name -like "*something*" } )

But that doesn’t work either.

Now for the solution, I’d love to point you out to a link on the world wide web where you can view the documentation, but honestly, I just can’t find it…

The solution is to replace the 3th line by this: $( … ), like this:

$foo = $( $foo | Where-Object { $_.Name -like "*something*" } )

And that works.

Happy coding & have a good one,

-Kristof

Powershell Remove-Item and symbolic links

Let’s say you’ve got a symbolic link which points to another folder and you want to delete the symbolic link through Powershell. Prepare for some weird stuff! Consider the following test script:

New-Item "SymbolicTest" -Type Directory
Set-Location "SymbolicTest"
New-Item "Source" -Type Directory
Set-Location "Source"
New-Item "Test1.txt"  -Type File
New-Item "Test2.txt"  -Type File
New-Item "Test3.txt"  -Type File
Set-Location "../"
# now some cmd since Powershell can't natively create symbolic links
cmd /c mklink /J `"Target`" `"Source`" 
ls

Let’s check the structure: dir in powershell Okay, so no difference, both are considered directories. Now let’s check with cmd: dir in cmd Okay, weird, cmd understands it. Now let’s try to remove the Target link. Remember, we only want to delete the link, not the contents of the Target folder (and thus implicitly not the contents of the Source folder). With Remove-Item you can delete items. Let’s try it out:

Remove-Item .\Target

This yields the following output:

deletion powershell

Wait what? Deletion of the children. Just for the sake of it I typed Y. This yielded another error: deletion powershell force prompt Okay, again, with -Force powershell content gone Target was gone, however also the contents of Source. Auch? Now what? Well, reading through the documentation of Remove-Item I found nothing about symbolic links.

So what now? Seems that Powershell doesn’t have knowledge about symbolic links. It wasn’t taken into account. He just follows the link like it’s a normal directory.

So what’s the solution? I decided to use the good old cmd (from Powershell!):

cmd /c rmdir .\Target

rmdir works!

Marvelous. Problem solved!

Actually, in retrospect, since I needed to use cmd to create the link, it would have made more sense to also just delete the link with cmd!

Have a good one,

-Kristof

Powershell: switch is not boolean

I spent a lot of time on this, mostly because of the lack of documentation on Powershell, I wonder why there isn’t one like for C#.

Anyway, back to the title, 2 different words, their behavior in a function is the same, but the invocation is not. Let me show you:

function MyAwesomeFunction
{
    param
    (
        [string] $foo,
        [string] $bar,
        [switch] $someVariable 
    )

    Write-Host "someVariable = $someVariable"

    if($someVariable)
    {
        Write-Host $foo
    }
    else
    {
        Write-Host $bar
    }
}

MyAwesomeFunction -foo "Foo" -Bar "Bar" 

Invocation of this script yields the following result:

PS D:\Personal\Desktop> D:\Personal\Desktop\test.ps1
someVariable = False
Bar

Now if we change the ‘switch’ on line 7 to ‘bool’:

function MyAwesomeFunction
{
    param
    (
        [string] $foo,
        [string] $bar,
        [bool] $someVariable #or boolean
    )

    Write-Host "someVariable = $someVariable"

    if($someVariable)
    {
        Write-Host $foo
    }
    else
    {
        Write-Host $bar
    }
}

MyAwesomeFunction -foo "Foo" -Bar "Bar" 

The result is the following

PS D:\Personal\Desktop> D:\Personal\Desktop\test.ps1
someVariable = False
Bar

Right, the same. Both times the variable ‘someVariable’ is False.

However, when we want to set the variable ‘someVariable’, then the invocation differs. If we invoke it like this on the bool version:

MyAwesomeFunction -foo "Foo" -Bar "Bar" -someVariable $true

That works fine.

If we invoke that one on the switch version, we are presented with the following error:

Positional argument error

So I would suggest that you use the bool version for internal functions in modules, ones that when you can explicitly set the someVariable to true or false, whereas the switch version is more useful of external use when you’re invoking a module and want to print more messages (like -Verbose), no need to print -Verbose $true.

Have a good one,

-Kristof

Null lists behavior in Powershell

Powershell is weird. We know that.

But this blows my mind.

Consider the following line of code:

$result = Get-ChildItem "foo*"

foreach($bar in $result)
{
	Write-Host ("Item name: {0}" -f $bar.FullName)
}

What will this print?

When a matching file is found it will print its full name.

What if it doesn’t print? Every sane person will say: nothing. Powershell is not sane. This is the result:

Property 'FullName' cannot be found on this object. Make sure that it exists.
At C:\Users\Kristof Mattei\Desktop\test.ps1:9 char:39
+     Write-Host ("Item name: {0}" -f $bar. <<<< FullName)
    + CategoryInfo          : InvalidOperation: (.:OperatorToken) [], RuntimeException
    + FullyQualifiedErrorId : PropertyNotFoundStrict

What?

Why on earth would the foreach start?

The list is $null, not even empty. Do I need to prefix every single foreach with a null check to make sure it doesn’t start?