Agile Ramblings

Home » Development

Category Archives: Development

Getting Docker running on Windows 10

Just a quick post about a couple things I’ve learned yesterday and today.

Docker is now available to run on Windows 10. I’m not going to go into the details as they are better covered in other posts, but I’ll share the steps I followed to get Docker running on my Windows 10 laptop.

Docker-Windows-10

Visit David Wesst’s Blog post (cross-posted to Western Devs)

Dave was the first of the Western Dev guys to talk about trying to get Docker working on Windows 10. He blogged about his adventure and that is where I started.

http://blog.davidwesst.com/2015/08/Docker-on-Windows-10-Problems/

http://www.westerndevs.com/docker-on-windows-10-problems/

Visit the Docker GitHub site

Dave’s post directed me towards a couple other places. Namely, the Docker issues GitHub site and the Docker windows installation page.

https://docs.docker.com/installation/windows/

This was pretty important because it was where the conversation about getting Docker (more accurately, VirtualBox) running on Windows 10. There is an issue in VirtualBox (current stable build) that does not allow it to work on Windows 10. This issue has been resolved in a Test build. The link to the test build is here.

https://www.virtualbox.org/wiki/Testbuilds

I didn’t actually need to go to the VirtualBox website to get the build because the latest test version of the Docker for Windows installer has the test version of VirtualBox already inside of it. You can find the link to the current test installer here.

https://github.com/docker/toolbox/issues/77

Follow the start up direction

The next thing I did was follow all of the start-up directions from the docker windows install documents. VirtualBox was installed, all of the Docker Toolbox items where installed, and so I fired it all up. And it didn’t work. What was going on? The VM very quickly informed me that it couldn’t find a 64bit cpu/os which is required to run docker.

This kernel requires an x86-64 CPU, but only detected an i686 CPU. Unable to boot – please use a kernel appropriate for your CPU

Well, that was weird. I have an modern laptop (Dell XPS 15) running 64 bit Windows 10 Enterprise. What could be the problem? Google Foo to the rescue!

First I found posts suggesting that the CPU Intel Virtualization Technologies were not enabled. I didn’t think that was true because I had already been running some HyperV machines on my laptop, but I did re-boot into my BIOS and ensure that Intel VT-x/AMD-V where enabled. They were.

So google a bit more, and I find that Virtual Box might need me to change the “type” of the VM to “Other” and the OS to “Other/64bit” or something like that. But interestingly enough, those were not options that I had in the VM.

VirtualBox-OS-Options

This screenshot was taken after the fix (which I’m getting to) but originally, none of the 64 bit versions of the OSes were available as a choice.

VirtualBox-OS-bit-options

One last thing I found was to remove the HyperV feature from Windows 10, but that wasn’t a viable option for me. I have some HyperV virtual machines that I run (and need to run) so I didn’t even explore that option.

At this point, I worked around for a bit and then gave up for the evening. Better to sleep on it and see if I could start fresh in the morning.

Scott Hanselman to the Rescue

I’m not sure what search I did in the morning that got me to Scott Hanselman’s post. I should really just always go there first because I find so much good information about Windows development (native and cross-platform) there. But specifically, it was this post that finally solved my problem.

http://www.hanselman.com/blog/SwitchEasilyBetweenVirtualBoxAndHyperVWithABCDEditBootEntryInWindows81.aspx

I didn’t know this until today, but as it turns out, HyperV and VirtualBox will not run together side-by-side in 64 bit modes. And Scott’s blog post about rebooting to a hypervisorlaunchtype off mode of Windows 8.1 worked flawlessly for Windows 10. So I didn’t have to un-install the HyperV feature, but as it turns out, I did have to disable HyperV. I’m sure glad I don’t have to add/remove it daily though!

Final Thoughts

So that was it! Thanks to David Wesst, WesternDevs, Docker and Scott Hanselman, I now have Docker running on my Windows 10 laptop. Just not at the same time as my HyperV virtual machines. 😀

Using PowerShell to Set Your Azure SQL firewall rule

If you’ve read a couple of my recent blog posts, you’ll see that I’ve been working in PowerShell a lot lately. I’ve also been working with Azure a lot lately as well and I’m getting opportunities to put those two things together.

Since my laptop is moving around a lot and occasionally my home IP address changes, I do need to update my Azure SQL Firewall rule to allow my computer at my current IP address to talk to my Azure SQL database server.

Azure SQL Database Firewall

I’ve added 4 simple functions to my .\profile.ps1 script that makes this job really easy.

function Set-MyAzureFirewallRule {
    $response = Invoke-WebRequest ifconfig.me/ip
    $ip = $response.Content.Trim()
    New-AzureSqlDatabaseServerFirewallRule -StartIPAddress $ip -EndIPAddress $ip -RuleName <Name of Rule> -ServerName <your database server name here>
}
function Update-MyAzureFirewallRule{
    $response = Invoke-WebRequest ifconfig.me/ip
    $ip = $response.Content.Trim()
    Set-AzureSqlDatabaseServerFirewallRule -StartIPAddress $ip -EndIPAddress $ip -RuleName <Name of Rule> -ServerName <your database server name here>
}
function Remove-MyAzureFirewallRule{
    Remove-AzureSqlDatabaseServerFirewallRule -RuleName <Name of Rule> -ServerName <your database server name here>
}
function Get-MyAzureFirewallRule{
    Get-AzureSqlDatabaseServerFirewallRule -RuleName <Name of Rule> -ServerName <your database server name here>
}

Get the Azure PowerShell Module

The first thing you’ll need to do if you want to do any work with Azure via PowerShell is download and install the Azure PowerShell modules.

Install And Configure Azure PowerShell

Once you’ve done this, you’ll be able to run Azure CommandLets in your PowerShell session.

How to get your IP address

Since many times I’m behind a router that is doing NAT translations, knowing my IP address isn’t as simple as typing Get-NetIPAddress | Format-Table or ipconfig in a console. That will tell me what my computer thinks the IP address is in my local network, but that isn’t what Azure will see. Azure will see the IP address of my cable modem.

In order to find out what my IP address is from an external perspective, I need the help of a little service called ifconfig.me tell me what my IP address is externally. If you make the whole Url ifconfig.me/ip you will get a simple text response from them with your IP address. Just give that Url a click and try it out. If you view the page source, you’ll see that only text was returned.

Putting it all together

So now we have the Azure PowerShell modules and we know about ifconfig.me. All we need now is the put the two together into one of our functions. I’ll use my first function as the example. You’ll be able to follow the rest after I describe this one.

function Set-MyAzureFirewallRule {
    $response = Invoke-WebRequest ifconfig.me/ip
    $ip = $response.Content.Trim()
    New-AzureSqlDatabaseServerFirewallRule -StartIPAddress $ip -EndIPAddress $ip -RuleName <Name of Rule> -ServerName <your database server name here>
}

The first line is the PowerShell (non-Azure) CmdLet Invoke-WebRequest ifconfig.me/ip. This will call ifconfig.me/ip and get a response, trapped in the $response variable.

In the next line, I clean up the response a little bit using some .Net string functions to move my IP address into the $ip variable.

Finally, I call the Azure PowerShell CmdLet to create a new Firewall rule in my Azure account.

You will have to have followed the instructions in Azure PowerShell Install and Configure to set up the authentication to allow this PowerShell session to access your Azure subscription.

The other three variations of this function are for completeness. You will actually probably use the Update-MyAzureFirewallRule most since you’ll set-up the Firewall rule once the first time and then you’ll just need to update it whenever your IP address changes.

Final Thoughts

I hope this post makes it easier for you to access your SQL Azure database server from your laptop, where ever it may have moved. Once you’ve set up the rule, you’ll be able to access your database server from the tools in Visual Studio, SQL Server Management Studio, or any other tool you prefer to use to work with your Azure SQL Server.

Enjoy!!

Building a TFS 2015 PowerShell Module using Nuget

Update: Unwittingly, I hadn’t tested my Nuget approach on a server with no Visual Studio or TFS installations on it and I’ve missed a couple assemblies that are required when loading the TFS Object model. I’ve updated the line of code in my samples, but just in case, here is the new version of the line in question.

$net45Dlls = $allDlls | ? {$_.PSPath.Contains("portable") -ne $true } | ? {$_.PSPath.Contains("resources") -ne $true } | ? { ($_.PSPath.Contains("net45") -eq $true) -or ($_.PSPath.Contains("native") -eq $true) -or ($_.PSPath.Contains("Microsoft.ServiceBus") -eq $true) }

The update is the addition of two -or statements to the last inclusive where clause.

I’ve also slightly changed the Import-TfsAssemblies function to include a try/catch block for better error reporting.

Original Start

With the release of Visual Studio 2015 on July 20, 2015, we can talk about and explore a lot of really cool things that are happening with Visual Studio (VS) and Team Foundation Server (TFS). One of the things that has been a bit of a pain when managing a TFS on-premises installation has been the necessity of installing Visual Studio to get the TFS client object model on your administrative workstation. With the explosive use of PowerShell to manage all things Microsoft, this has been a bit of a drag on using PowerShell for TFS work. There are PowerShell modules for TFS in the TFS Power Tools, but sometimes you need the power that comes with using the TFS Object Model. Which meant that you had to install Visual Studio. I’m really glad to say that is no longer the case. With the release of TFS 2015, the TFS Object Model is now available on Nuget! With our trusty nuget.exe, we can now get the TFS object model from a trusted source, without violating any license terms, to use in our own TFS PowerShell modules. I’m not going to profess to be a PowerShell wizard so I hope I’m not breaking any community best practices too badly. I’m more than happy to adapt my implementation if I get feedback on better ways of doing things! It should also be noted that I’m using PowerShell 4. This is located in the Windows Managment Framework 4 download (http://www.microsoft.com/en-ca/download/details.aspx?id=40855), a free download from Microsoft. I don’t think you’ll have any problems upgrading from previous versions of PowerShell but I’m not going to any assurances. Let’s start walking through building a TFS PowerShell module!

Create A PowerShell Module

I’m not going to go into a lot of details, but the basic steps to creating your PowerShell module are:

  1. Navigate to %USERPROFILE%\My Documents\WindowsPowerShell\Modules
  2. Create a folder called MyTfsModule
  3. In the MyTfsFolder, create a file called MyTfsModule.psm1

It is important that the name of the Module folder and the Module file are the same. Otherwise, you won’t be able to load your module. This one requirement tripped me up for a while when I started writing PowerShell modules.

Module-Specific Variables And Helper Functions

There are a few module specific variables that we need to set when the module loads and a Helper function that I use for getting/creating folders. You can put these at the top of your MyTfsModule.psm1 file.

Write-Host "Loading MyTfsModule"
#Module location folder
$ModuleRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
#where to put TFS Client OM files
$omBinFolder = $("$ModuleRoot\TFSOM\bin\")

# TFS Object Model Assembly Names
$vsCommon = "Microsoft.VisualStudio.Services.Common"
$commonName = "Microsoft.TeamFoundation.Common"
$clientName = "Microsoft.TeamFoundation.Client"
$VCClientName = "Microsoft.TeamFoundation.VersionControl.Client"
$WITClientName = "Microsoft.TeamFoundation.WorkItemTracking.Client"
$BuildClientName = "Microsoft.TeamFoundation.Build.Client"
$BuildCommonName = "Microsoft.TeamFoundation.Build.Common"

function New-Folder() {
    <# .SYNOPSIS This function creates new folders .DESCRIPTION This function will create a new folder if required or return a reference to the folder that was requested to be created if it already exists. .EXAMPLE New-Folder "C:\Temp\MyNewFolder\" .PARAMETER folderPath String representation of the folder path requested #>

     [CmdLetBinding()]
     param(
         [parameter(Mandatory=$true, ValueFromPipeline=$true)]
         [string]$folderPath
     )
    begin {}
    process {
        if (!(Test-Path -Path $folderPath)){
            New-Item -ItemType directory -Path $folderPath
        } else {
            Get-Item -Path $folderPath
        }
    }
    end {}
} #end Function New-Directory

First We Get Nuget

The first thing we need to do is get the Nuget.exe from the web. This is very easily down with the following PowerShell function

function Get-Nuget(){
    <# .SYNOPSIS This function gets Nuget.exe from the web .DESCRIPTION This function gets nuget.exe from the web and stores it somewhere relative to the module folder location #>
    [CmdLetBinding()]
    param()

    begin{}
    process
    {
        #where to get Nuget.exe from
        $sourceNugetExe = "http://nuget.org/nuget.exe"

        #where to save Nuget.exe too
        $targetNugetFolder = New-Folder $("$ModuleRoot\Nuget")
        $targetNugetExe = $("$ModuleRoot\Nuget\nuget.exe")

        try
        {
            # check if we have gotten nuget before
            $nugetExe = $targetNugetFolder.GetFiles() | ? {$_.Name -eq "nuget.exe"}
            if ($nugetExe -eq $null){
                #Get Nuget from a well known location on the web
                Invoke-WebRequest $sourceNugetExe -OutFile $targetNugetExe
            }
        }
        catch [Exception]
        {
            echo $_.Exception | format-list -force
        }

        #set an alias so we can use nuget syntactically the way we normally would
        Set-Alias nuget $targetNugetExe -Scope Global -Verbose
    }
    end{}
}

Ok! When this function is invoked, we should now see a nuget.exe appear at:

%USERPROFILE%\My Documents\WindowsPowerShell\Modules\MyTfsModule\Nuget\Nuget.exe

Using Nuget to get TFS Client Object Model

Now that we have nuget, we need to get the TFS Client Object model from nuget.

function Get-TfsAssembliesFromNuget(){
    <# .SYNOPSIS This function gets all of the TFS Object Model assemblies from nuget .DESCRIPTION This function gets all of the TFS Object Model assemblies from nuget and then creates a bin folder of all of the net45 assemblies so that they can be referenced easily and loaded as necessary #>
    [CmdletBinding()]
    param()

    begin{}
    process{
        #clear out bin folder
        $targetOMbinFolder = New-Folder $omBinFolder
        Remove-Item $targetOMbinFolder -Force -Recurse
        $targetOMbinFolder = New-Folder $omBinFolder
        $targetOMFolder = New-Folder $("$ModuleRoot\TFSOM\")

        #get all of the TFS 2015 Object Model packages from nuget
        nuget install "Microsoft.TeamFoundationServer.Client" -OutputDirectory $targetOMFolder -ExcludeVersion -NonInteractive
        nuget install "Microsoft.TeamFoundationServer.ExtendedClient" -OutputDirectory $targetOMFolder -ExcludeVersion -NonInteractive
        nuget install "Microsoft.VisualStudio.Services.Client" -OutputDirectory $targetOMFolder -ExcludeVersion -NonInteractive
        nuget install "Microsoft.VisualStudio.Services.InteractiveClient" -OutputDirectory $targetOMFolder -ExcludeVersion -NonInteractive

        #Copy all of the required .dlls out of the nuget folder structure 
        #to a bin folder so we can reference them easily and they are co-located
        #so that they can find each other as necessary when loading
        $allDlls = Get-ChildItem -Path $("$ModuleRoot\TFSOM\") -Recurse -File -Filter "*.dll"

        # Create list of all the required .dlls
        #exclude portable dlls
        $requiredDlls = $allDlls | ? {$_.PSPath.Contains("portable") -ne $true } 
        #exclude resource dlls
        $requiredDlls = $requiredDlls | ? {$_.PSPath.Contains("resources") -ne $true } 
        #include net45, native, and Microsoft.ServiceBus.dll
        $requiredDlls = $requiredDlls | ? { ($_.PSPath.Contains("net45") -eq $true) -or ($_.PSPath.Contains("native") -eq $true) -or ($_.PSPath.Contains("Microsoft.ServiceBus") -eq $true) }
        #copy them all to a bin folder
        $requiredDlls | % { Copy-Item -Path $_.Fullname -Destination $targetOMBinFolder}
    }
    end{}
}

This function does a could things. First it cleans out the existing bin folder, if it exists. Then it goes to nuget to get all of the packages that are available there. They are:

  1. http://www.nuget.org/packages/Microsoft.VisualStudio.Services.Client/
  2. http://www.nuget.org/packages/Microsoft.VisualStudio.Services.InteractiveClient/
  3. http://www.nuget.org/packages/Microsoft.TeamFoundationServer.Client/
  4. http://www.nuget.org/packages/Microsoft.TeamFoundationServer.ExtendedClient/

I use a number of switches on my invocation of the nuget.exe.

  • -OutputDirectory – This sets the output directory for the nuget activities
  • -ExcludeVersion – This tells Nuget not to append version numbers to package folders
  • -NonInteractive – Don’t prompt me for anything

The next part seems a bit verbose, but I’m leaving it that way as an example of achieving my intent in case you want to achieve something else. I am intending to get all of the net45, non-portable, base language (non-resource) assemblies from the directory structure that is created by nuget when getting the packages. In order to do that I:

  1. Find all .dll files in the directory structure, recursively
  2. Exclude .dll files that have “portable” in their path
  3. Exclude .dll files that have “resource” in their path
  4. Include only .dll files that have “net45” in their path

After I’ve narrowed it down to that list of .dll files, I copy them all to the TFSOM\bin folder where they will be referenced from. This also allows them to satisfy their dependencies on each other as required when loaded.

Loading the TFS Object Models Assemblies

Now that we’ve retrieved the TFS Object model, and tucked it away in a bin folder we can find, we are now ready to load these assemblies into the PowerShell session that this module is in.

function Import-TFSAssemblies() {
    <# .SYNOPSIS This function imports TFS Object Model assemblies into the PowerShell session .DESCRIPTION After the TFS 2015 Object Model has been retrieved from Nuget using Get-TfsAssembliesFromNuget function, this function will import the necessary (given current functions) assemblies into the PowerShell session #>
    [CmdLetBinding()]
    param()

    begin{}
    process
    {
        $omBinFolder = $("$ModuleRoot\TFSOM\bin\");
        $targetOMbinFolder = New-Folder $omBinFolder;

        try {
            Add-Type -LiteralPath $($targetOMbinFolder.PSPath + $vsCommon + ".dll")
            Add-Type -LiteralPath $($targetOMbinFolder.PSPath + $commonName + ".dll")
            Add-Type -LiteralPath $($targetOMbinFolder.PSPath + $clientName + ".dll")
            Add-Type -LiteralPath $($targetOMbinFolder.PSPath + $VCClientName + ".dll")
            Add-Type -LiteralPath $($targetOMbinFolder.PSPath + $WITClientName + ".dll")
            Add-Type -LiteralPath $($targetOMbinFolder.PSPath + $BuildClientName + ".dll")
            Add-Type -LiteralPath $($targetOMbinFolder.PSPath + $BuildCommonName + ".dll")
        } 
        catch {
            $_.Exception.LoaderExceptions | % { $_.Message }
        }
     }
     end{}
}

Putting the Object Model to Use

Now that we have the TFS Object Model loaded into this PowerShell session, we can use it! I’m going to show three functions. One that gets the TfsConfigurationServer object (basically your connection to the TFS server), one that gets the TeamProjectCollection Ids and a function that will get a list of all TFS Event Subscriptions on the server.

Get-TfsConfigServer

function Get-TfsConfigServer() {
    <#
    .SYNOPSIS
    Get a Team Foundation Server (TFS) Configuration Server object
    .DESCRIPTION
    The TFS Configuration Server is used for basic authentication and represents
    a connection to the server that is running Team Foundation Server.
    .EXAMPLE
    Get-TfsConfigServer "<Url to TFS>"
    .EXAMPLE
    Get-TfsConfigServer "http://localhost:8080/tfs"
    .EXAMPLE
    gtfs "http://localhost:8080/tfs"
    .PARAMETER url
     The Url of the TFS server that you'd like to access
    #>
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true)]
        [string]$url
    )
    begin {
        Write-Verbose "Loading TFS OM Assemblies for 2015 (14.83.0)"
        Import-TFSAssemblies
    }
    process {
        $retVal = [Microsoft.TeamFoundation.Client.TfsConfigurationServerFactory]::GetConfigurationServer($url)
        [void]$retVal.Authenticate()
        if(!$retVal.HasAuthenticated)
        {
            Write-Host "Not Authenticated"
            Write-Output $null;
        } else {
            Write-Host "Authenticated"
            Write-Output $retVal;
        }
    }
    end {
        Write-Verbose "ConfigurationServer object created."
    }
} #end Function Get-TfsConfigServer

This function takes a Url and returns an instance of a Microsoft.TeamFoundation.Client.TfsConfigurationServer. This connection object will be authenticated (via Windows Integrated Authentication). If you don’t have permission within the domain to administer the TFS server, you won’t be able to use the functions provided by the object model. The other functions require this connection in order to do their additional work.

Get-TfsProjectCollections


function Get-TfsTeamProjectCollectionIds() {
    <# .SYNOPSIS Get a collection of Team Project Collection (TPC) Id .DESCRIPTION Get a collection of Team Project Collection (TPC) Id from the server provided .EXAMPLE Get-TfsTeamProjectCollectionIds $configServer .EXAMPLE Get-TfsConfigServer "http://localhost:8080/tfs" | Get-TfsTeamProjectCollectionIds .PARAMETER configServer The TfsConfigurationServer object that represents a connection to TFS server that you'd like to access #>
    [CmdLetBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Microsoft.TeamFoundation.Client.TfsConfigurationServer]$configServer
    )
    begin{}
    process{
        # Get a list of TeamProjectCollections
        [guid[]]$types = [guid][Microsoft.TeamFoundation.Framework.Common.CatalogResourceTypes]::ProjectCollection
        $options = [Microsoft.TeamFoundation.Framework.Common.CatalogQueryOptions]::None
        $configServer.CatalogNode.QueryChildren( $types, $false, $options) | % { $_.Resource.Properties["InstanceId"]}
    }
    end{}
} #end Function Get-TfsTeamProjectCollectionIds

Get-TfsEventSubscriptions

We are using a 3rd party tool that subscribes to build events and we needed to know if it was releasing those subscriptions properly and also discover where this tool was running. We thought that the easiest way to do this was to look at all of the subscriptions in the TFS Project Collections in our AppTier.

#adapted from http://blogs.msdn.com/b/alming/archive/2013/05/06/finding-subscriptions-in-tfs-2012-using-powershell.aspx
function Get-TFSEventSubscriptions() {

    [CmdLetBinding()]
    param(
        [parameter(Mandatory = $true)]
        [Microsoft.TeamFoundation.Client.TfsConfigurationServer]$configServer
    )

    begin{}
    process{
        $tpcIds = Get-TfsTeamProjectCollectionIds $configServer
        foreach($tpcId in $tpcIds)
        {
            #Get TPC instance
            $tpc = $configServer.GetTeamProjectCollection($tpcId)
            #TFS Services to be used
            $eventService = $tpc.GetService("Microsoft.TeamFoundation.Framework.Client.IEventService")
            $identityService = $tpc.GetService("Microsoft.TeamFoundation.Framework.Client.IIdentityManagementService")

            foreach ($sub in $eventService.GetAllEventSubscriptions())
            {
                #First resolve the subscriber ID
                $tfsId = $identityService.ReadIdentity(
                    [Microsoft.TeamFoundation.Framework.Common.IdentitySearchFactor]::Identifier,
                    $sub.Subscriber,
                    [Microsoft.TeamFoundation.Framework.Common.MembershipQuery]::None,
                    [Microsoft.TeamFoundation.Framework.Common.ReadIdentityOptions]::None )

                if ($tfsId.UniqueName)
                {
                    $subscriberId = $tfsId.UniqueName
                }
                else
                {
                    $subscriberId = $tfsId.DisplayName
                }

                #then create custom PSObject
                $subPSObj = New-Object PSObject -Property @{
                    AppTier = $tpc.Uri
                    ID = $sub.ID
                    Device = $sub.Device
                    Condition = $sub.ConditionString
                    EventType = $sub.EventType
                    Address = $sub.DeliveryPreference.Address
                    Schedule = $sub.DeliveryPreference.Schedule
                    DeliveryType = $sub.DeliveryPreference.Type
                    SubscriberName = $subscriberId
                    Tag = $sub.Tag
               }

               #Send object to the pipeline. You could store it on an Arraylist, but that just
               #consumes more memory
               $subPSObj

               #This is another variation where we just add a property to the existing Subscription object
               #this might be desirable since it will keep the other members
               #Add-Member -InputObject $sub -NotePropertyName SubscriberName -NotePropertyValue $subscriberId
           }
       }
   }
   end{}
}

All Done

We are now all done creating our initial MyTfsModule implementation! We should be able to load it up now and give it a spin! MyTfsModule_In_Action I’ve obscured the name of my running module and TFS server, but in those spots, just use the name of your module and TFS server.

Import-Module MyTfsModule
$configServer = Get-TfsConfigServer http://<name of your TFS server>:8080/tfs
$allEventsOnServer = Get-TfsEventSubscriptions $configServer
$allEventsOnServer.Length

Final Thoughts

The key takeaway from this post was that it is great that we can now get the TFS Object Model from Nuget. Still a bit of a pain to sort and move the downloaded assemblies around, but this is because we I am using PowerShell and not building some sort of C#-based project in Visual Studio which would handle the nuget packages much more elegantly. I hope this post gives you the information you need to go off and create your own TFS PowerShell module without having to install Visual Studio first! p.s. I do have a version of this module that loads the assemblies from the install location of Visual Studio. I’ll visit that shortly in another blog post.

Using IE Automation in PowerShell to Simplify Browser Based Tasks

As a consultant, one of the things that I need to do regularly is log into my client’s WiFi networks. Sometimes this is a once per month task, sometimes it is a daily task. It was a daily version of this task that made me look into doing this a bit quicker. Opening Internet Explorer (or any browser) and then navigating to the page, typing in all of my credentials, and then submitting the request is a fairly monotonous task, and it isn’t very quick.

Now a days, I almost always have a PowerShell window open, and because of another little experiment I did with PowerShell and IE, I thought it should be easy to automate my WiFi network login. So I that is what I set out to do.

The way that I’m currently working in PowerShell is to create a .ps1 file to do my development in. That way I can version control the file, and keep it separate from other things that are working or in progress. So in this case, I made a PowerShell script file called Login-GuestWifi.ps1. In this file, I just started typing lines of PowerShell script and eventually I would move it into a CmdLet or a function somewhere else.

The first line in the PowerShell script is a call to create an Internet Explorer Application.

$ie = new-object -ComObject "InternetExplorer.Application"

Now that you’ve got IE in your PowerShell code, you need to figure out what to do with this. This is going to require a little bit of work in the browser so you’re going to have to open a browser and navigate to the page you’re going to be working with. In my case, this was an internal IP address that I was re-directed to when using the browser for the first time on the guest WiFi network.

http://10.10.10.10/guest/wifi-guest.php  <– Example URL

image

Once I’ve navigated there, I press F12 to get to the developer tools of my browser. (I’ll use IE for my examples)

image

Using the Dev Tools, I’m going to discover what the fields I need to fill in are (their id or class). In this case, I found fragments of ids that were not generated. I took those fragments and put them into my PowerShell code along with URL of the login page.

$requestUri = http://10.10.10.10/guest/wifi-guest.php
$userIdFragment = "weblogin_user";
$passwordIdFragment = "weblogin_password";
$acceptTermsInputFragment = "weblogin_visitor_accept_terms"
$buttonIdFragment = "weblogin_submit";

I now have details of where to go and ability to find the elements on the page that I’m interested in. I’m going to now invoke some methods on the IE Application instance I have to navigate to the Url.

#$ie.visible = $true
$ie.silent = $true
$ie.navigate($requestUri)
while($ie.Busy) { Start-Sleep -Milliseconds 100 }

The first two lines indicate how IE is supposed to behave in two ways and the first one is commented out.

  1. Show the instance of IE. With this line commented out, we get a “headless” browsing experience with no visible window or rendering. Visible Property – MSDN
  2. Do not show any dialogs that may pop up. Silent Property – MSDN

The next instruction tells IE to navigate to the Url provided.

The 4th line of this script fragment is interesting. We need to wait for IE to actually do the navigation. If we don’t add this line, the PowerShell script will happily continue executing much faster than IE will retrieve and load the page into the Document Object Model (DOM) and the rest of your script will probably fail.

After IE has loaded up the DOM, we can now find our elements, give them values, and click the Submit button.

$doc = $ie.Document
$doc.getElementsByTagName("input") | % {
    if ($_.id -ne $null){
        if ($_.id.Contains($buttonIdFragment)) { $btn = $_ }
        if ($_.id.Contains($acceptTermsInputFragment)) { $at = $_ }
        if ($_.id.Contains($passwordIdFragment)) { $pwd = $_ }
        if ($_.id.Contains($userIdFragment)) { $user = $_ }
    }
}

$user.value = "<user name here>"
$pwd.value = "<password here>"
$at.checked = "checked"
$btn.disabled = $false
$btn.click()
Write-Verbose "Login Complete"

One interesting thing about IE automation is that any JavaScript or page behaviours that we would expect to execute don’t seem to run, so we need to explicitly enable the submit button in the event that it was not enabled until all of the fields were entered and the accept terms of use checkbox was clicked.

And that’s it! I now have a PowerShell script that runs in seconds and logs me into the client’s guest WiFi network.

image

 

As a final task, I took the code in my Login-GuestWifi.ps1, converted it to a function and placed it in my ./profile.ps1 file that gets invoked any time a PowerShell session is started on my machine.

It should be noted that the UserName and Password, in my case, were not secured in any fashion other than being only physically stored on my machine in my scripts file. I never checked my credentials into source control and I had no need to put them anywhere else. If needed, I could secure them but that wasn’t necessary. These are not domain credentials and are only giving people access to the guest WiFi network.

Final Thoughts

My goal with this post was to exposed you to the idea of using PowerShell to automate simple web-based tasks in Internet Explorer. I’ve recently been using PowerShell a lot and I’ve just been continuously impressed with how powerful it is. So go give it a try!

Storing Secrets In Azure Guest Post on Canadian Developer Connection Blog

I was recently asked to contribute to the Microsoft Canadian Developer Connection blog and the post has gone live! This post details how to Store Your Secrets in an Azure Websites App Settings! Go check it out!

http://blogs.msdn.com/b/cdndevs/archive/2015/03/20/storing-your-secrets-in-azure-websites-app-settings.aspx