Adam's "Blog"

That's all, really.

Commit All Changes in a Solution Using NuGet PowerShell

When Visual Studio 2012 dropped support for macros, I lost a few of my favorite tools — for a while. John Robbins of Wintellect realized you could use PowerShell as a macro language and run your functions from the Package Manager (NuGet) Console. I ported over some macros at once, and have been happily using them ever since. (I still don’t know how to bind a hotkey to a “macro”… but I bound a key to the Package Manager Console, and created short aliases for my functions, so running them is not hard.)

One of my most-used functions is Tortoise-CommitSolution, to open the TortoiseSVN or TortoiseGit Commit dialog, and pass it all the paths in the solution. (This helps me to avoid the curse of the missed commit.) Below is the script I use for it.

First, check if you have the NuGet Package Manager installed. Open Visual Studio (2012 or 2013) and go to Tools, Library Package Manager, Package Manager Console. If this works, you’re all set.

But if there is no “Library Package Manager” submenu under the Tools menu, then you need to install it. Go to Tools, Extensions and Updates; click “Online”, and in the search box, type “nuget package manager”. The first hit should be “NuGet Package Manager”; go ahead and click Download and install it, and restart Visual Studio as prompted.

Now, to use Tortoise-CommitSolution, download the script and save it somewhere such as My Documents\WindowsPowerShell. Next, you’ll want to edit your profile to dot-source that script and to make a short alias for the function. To do this, type notepad $profile in the Package Manager Console. Add these two lines (adjusting the path in the first one to point at the place where you saved the script):

    . $Env:UserProfile\Documents\WindowsPowerShell\commit.ps1
    Set-Alias commit Tortoise-CommitSolution

Save your profile, and then run this in the Package Manager Console to reload it: . $profile

Now open a solution, and type commit in the Package Manager Console. Magic!

Here’s the script:

(commit.ps1) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# Tortoise-CommitSolution by Adam Coyne <adam@coyne.nu>
# Add a convenient alias (like "Set-Alias commit Tortoise-CommitSolution") for this to your ${Env:HOME}\Documents\WindowsPowerShell\NuGet_profile.ps1
function Tortoise-CommitSolution {
    [string[]]$projectpaths = Get-Project -All | foreach { [System.IO.Path]::GetDirectoryName($_.FullName) }
    $projectpaths += $dte.Solution.FullName
    if($projectpaths.length -gt 0) {
        $filename = Write-PathFile(Filter-PathArray($projectpaths))
        # Visual Studio is 32-bit, but I have 64-bit TortoiseSvn and TortoiseGit installed
        $programfiles = $Env:ProgramW6432
        if(!$programfiles) { $programfiles = $Env:ProgramFiles }
        if(Get-LocalOrParentPath .svn $projectpaths[0]) {
            & "$programfiles\TortoiseSVN\bin\tortoiseproc.exe" /command:commit /deletepathfile /pathfile:`"$filename`" /hwnd:$($dte.MainWindow.HWnd)
        } elseif(Get-LocalOrParentPath .git $projectpaths[0]) {
            & "$programfiles\TortoiseGit\bin\tortoisegitproc.exe" /command:commit /deletepathfile /pathfile:`"$filename`" /hwnd:$($dte.MainWindow.HWnd)
        }
    }
}

# Filter out paths that are contained within another path, so modified files won't show up twice in the Commit dialog.
function Filter-PathArray([string[]]$paths) {
    [string[]]$filteredprojectpaths = @()
    [string]$lastCommonParentPath = "*" # an initial value that won't match

    foreach($p in $paths | Sort-Object) {
        if(-not $p.StartsWith($lastCommonParentPath)) {
            $filteredprojectpaths += $p
            $lastCommonParentPath = $p + "\"
        }
    }
    return $filteredprojectpaths
}

# Tortoise's pathfile contains paths delimited by line feeds and with a trailing line feed, and is formatted as Unicode with no BOM.
function Write-PathFile([string[]]$files) {
    $filename = [System.IO.Path]::GetTempFileName()
    $contents = [string]::join("`n", $files) + "`n"
    $bytes = [System.Text.Encoding]::Unicode.GetBytes($contents)
    Set-Content -Encoding byte $filename $bytes
    return $filename
}

# From https://github.com/dahlbyk/posh-git
function Get-LocalOrParentPath($path, $startPath = '.') {
    $checkIn = Get-Item $startPath
    while ($checkIn -ne $NULL) {
        $pathToTest = [System.IO.Path]::Combine($checkIn.fullname, $path)
        if (Test-Path $pathToTest) {
            return $pathToTest
        } else {
            $checkIn = $checkIn.parent
        }
    }
    return $null
}