8
\$\begingroup\$

I wrote a script to perform svn update on all the SVN repositories inside a folder. It would have been great if the folder containing all these repositories itself was a repository, but it was not setup like that, which is why I opted to write this script.

$timestamp = get-date -f "yyyy-M-d-h-m-s"
$SvnLogPath="C:\out\svnlogs\"

if(test-path -path $SvnLogPath)
{
    write-host "SVN Log path doesn't exist. So I am creating it . . ."
    new-item -type directory $SvnLogPath | out-null
}

$logfile = "${SvnLogPath}svn_update_${timestamp}.log"
new-item -type file $logfile | out-null

$BaseDir="C:\TRUNK\"

$RepositoryList = "Alpha","Bravo","Charlie","Delta","Echo","Foxtrot","Golf","Hotel"

# BUILDING FULL PATHS
$FullPaths = $RepositoryList | %{ $BaseDir+$_}

# ISSUING SVN UPDATE TO EACH DIR
$FullPaths | % {svn update $_ 2>&1 | ft -AutoSize -Wrap | Out-File -Append $logfile } 

# OPEN LOG FILE AFTER THE COMPLETION
notepad $logfile

Can this script be further simplified and written more idiomatically?

PS: I am using only svn update without credentials as they are stored in the shell already.

\$\endgroup\$

2 Answers 2

8
\$\begingroup\$

Reusable Commands

One of the most important ideas in PowerShell is the Get/Update/Set pattern. A clear delineation between resources (nouns) and commands (verbs) helps to write code that is useful not only for scripting, but also in the interactive shell.

We only need a couple of new commands. First we need to Find the working copies under some directory, then Update each working copy. The list of approved verbs for PowerShell is a great resource for help with naming your commands. Let's use these names.

  • Find-SvnWorkingCopy (Find looks for an object in a container)
  • Update-SvnWorkingCopy (Update brings a resource up-to-date)

Functions and Modules

Now you're convinced that we should create reusable commands with these great new names. What next? PowerShell v2 introduced the concept of a module, which is just a file that contains functions. (Of course, you could just create a new script for each command but it helps to keep things tidy by bundling all of the functions together in one file.)

What should our functions look like? Yes they will need to take some parameters. But more importantly, they should return objects to the pipeline, even if they aren't a "getter" function! This makes it easier to review what impact a function had.

Some actual code

Luckily for us, somebody else already found a way to find all the svn working copies underneath a directory. We can reuse that.

SvnTools.psm1

function Find-SvnWorkingCopy
{
    Param (
        [Parameter(ValueFromPipeline=$true,Mandatory=$true)]
        $BasePath
    )
    process
    {
        $prev = "^$"
        Get-ChildItem -Recurse -Force -Include ".svn" -Path $BasePath |
            Where-Object {
                $_.PSIsContainer -and $_.Fullname.StartsWith($prev)-eq $false
            } |
            ForEach-Object {
                $prev=$_.Fullname.TrimEnd(".svn")
                $prev
            }
    }
}

I took some liberties with the command Update-SvnWorkingCopy. In addition to calling "svn update" like your original script, it also calls "svn info" and parses the results to find the current revision number and include that in the object returned to the pipeline.

SvnTools.psm1 (continued)

function Update-SvnWorkingCopy
{
    Param (
        [Parameter(ValueFromPipeline=$true,Mandatory=$true)]
        $WorkingCopy
    )
    process {
        svn update $WorkingCopy > $null
        [String[]] $result = svn info $WorkingCopy
        [String] $revStr = $result -like "Revision:*"
        [int] $revNumber = $revStr.Replace("Revision: ", "")
        New-Object -TypeName PSObject -Prop @{
            WorkingCopy=$WorkingCopy;
            Revision=$revNumber;
        } | Write-Output
    }
}

Now we can reap the benefits from creating these commands. Here's your original script, rewritten to take advantage of our new module using just one line.

UpdateMyWorkingCopies.ps1

Import-Module C:\path\to\module\SvnTools.psm1
Find-SvnWorkingCopy -BasePath "C:\TRUNK\" | Update-SvnWorkingCopy

You could easily redirect the output to a file. If you don't, it will show up on screen looking something like this.

WorkingCopy         Revision
-----------         --------
C:\TRUNK\Alpha      31
C:\TRUNK\Bravo      42
C:\TRUNK\Charlie    16

References

The Scripting Guy! blog on TechNet has a really excellent series about building your own PowerShell cmdlet that I recommend reading.

Coding Style

Bonus Points

For bonus points, install the module into one of the module path folders ($env:PSModulePath) so that you can import it from the shell more easily.

And if you want to get even more fancy, implement another command called Get-SvnWorkingCopy which parses all of the results from svn info. It should integrate nicely with the two commands we already created here.

\$\endgroup\$
1
  • \$\begingroup\$ This almost works perfectly! The only thing not quite right (here in 2021!) is that the Rev # stays the same throughout the loop. I'm going to see why it's not changing, and I'll be back. \$\endgroup\$ Sep 1, 2021 at 13:36
4
\$\begingroup\$

Personally, I would look to use:

$FullPaths = $RepositoryList | %{ Join-path $BaseDir $_ }

vs.

$FullPaths = $RepositoryList | %{ $BaseDir+$_}

You could alternatively also do something like:

$FullPaths = Join-path $BaseDir "*\.svn" | Resolve-Path | Split-Path

This would build a list of all subfolders of $BaseDir that have a .SVN folder.

Resolve path will return the path with ..SVN\ and split path will remove the folder.

If you have .LOG registered with Notepad, you could also use

Invoke-Item $logFile

This may be more flexible if someone else runs the script and would rather see output in a different editor.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.