CSharp-Watch.psm1
<#
.SYNOPSIS Watches the current path for changes to CSharp files and initiates a build of the project. .DESCRIPTION Watches the current directory and sub-directories for changes to C-Sharp files; initiating a build of the project to which the changed file belongs. .PARAMETER copytarget (Optional) defines an additional target directory where the updated dll gets copied .PARAMETER urltarget (Optional) defines a web url to hit after successful build/copy to warm up a site .EXAMPLE # Standard call, builds project > cd "D:\path\to\my\project" > D:\path\to\my\project> Start-CSharp-Watch > ------------------------- # Overload, builds project and copies dll to the supplied directory > cd "D:\path\to\my\project" > D:\path\to\my\project> Start-CSharp-Watch -copytarget "d:\path\to\my\website\bin" > ------------------------- # Overload, builds project, copies dll to the supplied directory and hits url > cd "D:\path\to\my\project" > D:\path\to\my\project> Start-CSharp-Watch -copytarget "d:\path\to\my\website\bin" -urltarget "http://localhost:50123" #> function Start-CSharp-Watch([Parameter(Mandatory=$false)][string]$copytarget, [Parameter(Mandatory=$false)][string]$urltarget) { write-host "CSharp-Watch is watching for file changes..." Import-Module -Name Invoke-MsBuild if ($copytarget) { # check the parameter is valid directory if ($copytarget -match "^[a-zA-Z]\:\\.+") { $global:copytarget = $copytarget Write-Host "CSharp-Watch will copy updated dlls to: '$global:copytarget'" } else { Write-Host "CSharp-Watch says, 'copytarget is an invalid path'" } } if ($urltarget) { # check the parameter is valid directory if ($urltarget -match "^http[s]?\:\/\/.+") { $global:urltarget = $urltarget Write-Host "CSharp-Watch will make a request after build/copy to: '$global:urltarget'" } else { Write-Host "CSharp-Watch says, 'urltarget is an invalid, should be in form e.g. http://localsite.me'" } } $existingEvents = get-eventsubscriber foreach ($item in $existingEvents) { if ($item.SourceObject.Path -eq $global:EventSourcePath) { Unregister-event -SubscriptionId $item.SubscriptionId write-host "Unsubscribed from: "$item.SourceObject.Path } } $global:FileChanged = $false # dirty... any better suggestions? $folder = get-location $global:EventSourcePath = $folder $filter = "*.*" $watcher = New-Object IO.FileSystemWatcher $folder, $filter -Property @{ IncludeSubdirectories = $true EnableRaisingEvents = $true } Register-ObjectEvent $watcher "Changed" -Action { $global:FileChanged = $true $mypath = $Event.SourceEventArgs.FullPath if ($mypath -match "(\.cs~|.cs$)") { $global:ChangedPath = $mypath } } > $null While ($true) { While ($global:FileChanged -eq $false){ # We need this to block the IO thread until there is something to run # so the script doesn't finish. If we call the action directly from # the event it won't be able to write to the console Start-Sleep -Milliseconds 250 } # a file has changed, run our stuff on the I/O thread so we can see the output # Visual Studio creates a temp file like Code.cs~98jfiodjf.tmp if ($global:ChangedPath -match "(\.cs~|.cs$)") { $localchangedpath = $global:ChangedPath $global:ChangedPath = "nowhere" write-host "File was changed: '$localchangedpath'" -f Green $pathParts = "$localchangedpath".Split("\\") For ($i = $pathParts.Length - 2; $i -gt 0; $i--) { $newPath = $pathParts[0..$i] -join "\" if (test-path $newPath) { $csproj = Get-ChildItem -path $newPath -filter *.csproj write-host "$i. trying: $newPath, csproj: $csproj" if ($csproj) { write-host "Found on $i, at $newPath, $csproj" break } } } if ("$csproj".EndsWith(".csproj")) { write-host "Ready: $newPath\$csproj" $buildresult = Invoke-MsBuild -Path "$newPath\$csproj" -Params "/target:Build /p:configuration=debug /p:PostBuildEvent= /verbosity:m" if ($buildresult.BuildSucceeded) { write-host "Build was successful"@(get-date -Format u) -ForegroundColor Green if ($global:copytarget -and $csproj) { # there's a copy target set, so copy the dll to there write-host "Copying the binaries to '$global:copytarget'" write-host "CSPROJ File directory at '$newPath'" write-host "CSPROJ File is '$csproj'" $dllname = $csproj -replace ".csproj" write-host "CURRENT Dll is called '$newPath\bin\*$dllName*.dll'" # pretty much hoping that first result will the be the right one! $targetdll = @(Get-ChildItem -Path "$newPath" -Name "*$dllName*.dll" -Recurse)[0] $targetpdb = @(Get-ChildItem -Path "$newPath" -Name "*$dllName*.pdb" -Recurse)[0] write-host "Target dll is at '$newPath\$targetdll' ... Target pdb is at '$newPath\$targetpdb'" $copyjob = start-job -Name copyjob -ScriptBlock { param([string]$dllsource, [string]$pdbsource, [string]$target) xcopy $dllsource $target /Y xcopy $pdbsource $target /Y } -ArgumentList @("$newPath\$targetdll", "$newPath\$targetpdb", "$global:copytarget") $copyjobevent = Register-ObjectEvent $copyjob StateChanged -MessageData "$newpath\$targetdll" -Action { Write-Host ('Job {0} complete (copy from {1} to {2})' -f $sender.Name, $Event.MessageData, $global:copytarget) -ForegroundColor DarkYellow $copyjobevent | Unregister-Event } } if ($global:urltarget) { Write-Host "Hit uri: '$global:urltarget'" $webjob = start-job { Invoke-WebRequest -uri "$global:urltarget" -TimeoutSec 180 } -Name webjob $webjobevent = Register-ObjectEvent $webjob StateChanged -Action { Write-Host ('Job {0} complete (requested uri: {1}).' -f $sender.Name, $global:urltarget) -ForegroundColor DarkYellow $webjobevent | Unregister-Event } } } } } # reset and go again $global:FileChanged = $false } } <# .SYNOPSIS Unsubscribes current path from the watch event. .DESCRIPTION Unsubscribes current path from the watch event. .EXAMPLE > cd "D:\path\to\my\project" > D:\path\to\my\project> Stop-CSharp-Watch #> function Stop-CSharp-Watch() { $existingEvents = get-eventsubscriber ForEach ($item in $existingEvents) { if ($item.SourceObject.Path -eq $global:EventSourcePath) { Unregister-event -SubscriptionId $item.SubscriptionId write-host "Unsubscribed from: '$item.SourceObject.Path'" } } break } |