├── .gitattributes ├── .gitignore ├── Apps ├── Apps.sln └── Apps │ ├── Apps.pssproj │ ├── Deploy-SPApp.Tests.ps1 │ ├── Deploy-SPApp.ps1 │ ├── Deploy-WebSite.ps1 │ ├── Install-SPAddIn │ ├── Install-SPAddIn.Tests.ps1 │ └── Install-SPAddIn.ps1 │ ├── New-AppPackage │ ├── New-AppPackage.Tests.ps1 │ └── New-AppPackage.ps1 │ ├── Replace-Tokens.ps1 │ ├── Set-AssemblyVersion.Tests.ps1 │ ├── Set-AssemblyVersion.ps1 │ └── Trust-SPApp.ps1 ├── DSCPublishing ├── DSCResourcesConfiguration.ps1 ├── Ensure-DSCResources.ps1 ├── Package.ps1 ├── README.md └── TFSBuild.proj ├── DevOps ├── DevOps.sln └── DevOps │ ├── Backup-Database.ps1 │ ├── Backup-Website.ps1 │ ├── DevOps.pssproj │ ├── Get-TFSRelease.ps1 │ ├── Get-VSTSRelease.ps1 │ └── Restore-Website.ps1 ├── README.md ├── Set-RepositoryPermission ├── Readme.md └── Set-RepositoryPermission.ps1 ├── TFS └── ReleaseVariables │ ├── Get-ReleasesWithVariable.ps1 │ ├── Readme.md │ └── Update-ReleaseVariables.ps1 └── TFSBuild ├── README.md ├── TFSBuild.sln └── TFSBuild ├── AssemblyVersion ├── AssemblyVersion.pssproj ├── README.md └── Set-AssemblyVersion │ ├── Set-AssemblyVersion.Tests.ps1 │ └── Set-AssemblyVersion.ps1 └── Start-Release └── Start-Release.ps1 /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | [Rr]eleases/ 14 | x64/ 15 | x86/ 16 | build/ 17 | bld/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | 21 | # Roslyn cache directories 22 | *.ide/ 23 | 24 | # MSTest test Results 25 | [Tt]est[Rr]esult*/ 26 | [Bb]uild[Ll]og.* 27 | 28 | #NUNIT 29 | *.VisualState.xml 30 | TestResult.xml 31 | 32 | # Build Results of an ATL Project 33 | [Dd]ebugPS/ 34 | [Rr]eleasePS/ 35 | dlldata.c 36 | 37 | *_i.c 38 | *_p.c 39 | *_i.h 40 | *.ilk 41 | *.meta 42 | *.obj 43 | *.pch 44 | *.pdb 45 | *.pgc 46 | *.pgd 47 | *.rsp 48 | *.sbr 49 | *.tlb 50 | *.tli 51 | *.tlh 52 | *.tmp 53 | *.tmp_proj 54 | *.log 55 | *.vspscc 56 | *.vssscc 57 | .builds 58 | *.pidb 59 | *.svclog 60 | *.scc 61 | 62 | # Chutzpah Test files 63 | _Chutzpah* 64 | 65 | # Visual C++ cache files 66 | ipch/ 67 | *.aps 68 | *.ncb 69 | *.opensdf 70 | *.sdf 71 | *.cachefile 72 | 73 | # Visual Studio profiler 74 | *.psess 75 | *.vsp 76 | *.vspx 77 | 78 | # TFS 2012 Local Workspace 79 | $tf/ 80 | 81 | # Guidance Automation Toolkit 82 | *.gpState 83 | 84 | # ReSharper is a .NET coding add-in 85 | _ReSharper*/ 86 | *.[Rr]e[Ss]harper 87 | *.DotSettings.user 88 | 89 | # JustCode is a .NET coding addin-in 90 | .JustCode 91 | 92 | # TeamCity is a build add-in 93 | _TeamCity* 94 | 95 | # DotCover is a Code Coverage Tool 96 | *.dotCover 97 | 98 | # NCrunch 99 | _NCrunch_* 100 | .*crunch*.local.xml 101 | 102 | # MightyMoose 103 | *.mm.* 104 | AutoTest.Net/ 105 | 106 | # Web workbench (sass) 107 | .sass-cache/ 108 | 109 | # Installshield output folder 110 | [Ee]xpress/ 111 | 112 | # DocProject is a documentation generator add-in 113 | DocProject/buildhelp/ 114 | DocProject/Help/*.HxT 115 | DocProject/Help/*.HxC 116 | DocProject/Help/*.hhc 117 | DocProject/Help/*.hhk 118 | DocProject/Help/*.hhp 119 | DocProject/Help/Html2 120 | DocProject/Help/html 121 | 122 | # Click-Once directory 123 | publish/ 124 | 125 | # Publish Web Output 126 | *.[Pp]ublish.xml 127 | *.azurePubxml 128 | # TODO: Comment the next line if you want to checkin your web deploy settings 129 | # but database connection strings (with potential passwords) will be unencrypted 130 | *.pubxml 131 | *.publishproj 132 | 133 | # NuGet Packages 134 | *.nupkg 135 | # The packages folder can be ignored because of Package Restore 136 | **/packages/* 137 | # except build/, which is used as an MSBuild target. 138 | !**/packages/build/ 139 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 140 | #!**/packages/repositories.config 141 | 142 | # Windows Azure Build Output 143 | csx/ 144 | *.build.csdef 145 | 146 | # Windows Store app package directory 147 | AppPackages/ 148 | 149 | # Others 150 | sql/ 151 | *.Cache 152 | ClientBin/ 153 | [Ss]tyle[Cc]op.* 154 | ~$* 155 | *~ 156 | *.dbmdl 157 | *.dbproj.schemaview 158 | *.pfx 159 | *.publishsettings 160 | node_modules/ 161 | 162 | # RIA/Silverlight projects 163 | Generated_Code/ 164 | 165 | # Backup & report files from converting an old project file 166 | # to a newer Visual Studio version. Backup files are not needed, 167 | # because we have git ;-) 168 | _UpgradeReport_Files/ 169 | Backup*/ 170 | UpgradeLog*.XML 171 | UpgradeLog*.htm 172 | 173 | # SQL Server files 174 | *.mdf 175 | *.ldf 176 | 177 | # Business Intelligence projects 178 | *.rdl.data 179 | *.bim.layout 180 | *.bim_*.settings 181 | 182 | # Microsoft Fakes 183 | FakesAssemblies/ 184 | -------------------------------------------------------------------------------- /Apps/Apps.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F5034706-568F-408A-B7B3-4D38C6DB8A32}") = "Apps", "Apps\Apps.pssproj", "{6CAFC0C6-A428-4D30-A9F9-700E829FEA51}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /Apps/Apps/Apps.pssproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | 2.0 6 | 6CAFC0C6-A428-4d30-A9F9-700E829FEA51 7 | Exe 8 | MyApplication 9 | MyApplication 10 | Apps 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug\ 17 | DEBUG;TRACE 18 | prompt 19 | 4 20 | 21 | 22 | pdbonly 23 | true 24 | bin\Release\ 25 | TRACE 26 | prompt 27 | 4 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Apps/Apps/Deploy-SPApp.Tests.ps1: -------------------------------------------------------------------------------- 1 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".") 3 | . "$here\$sut" 4 | 5 | Describe "Deploy-SPApp" { 6 | It "does something useful" { 7 | $true | Should Be $false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Apps/Apps/Deploy-SPApp.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] 2 | [OutputType([int])] 3 | Param 4 | ( 5 | [Parameter(Mandatory=$true, Position=0)] 6 | [ValidateNotNullOrEmpty()] 7 | [string]$AppName, 8 | 9 | [Parameter(Mandatory=$true, Position=1)] 10 | [ValidateNotNullOrEmpty()] 11 | [string]$WebUrl, 12 | 13 | [Parameter(Mandatory=$true, Position=2)] 14 | [ValidateNotNullOrEmpty()] 15 | [string]$RemoteAppUrl, 16 | 17 | [Parameter(Mandatory=$true, Position=3)] 18 | [ValidateNotNullOrEmpty()] 19 | [string]$DeployUserName, 20 | 21 | [Parameter(Mandatory=$true, Position=4)] 22 | [ValidateNotNullOrEmpty()] 23 | [string]$DeployPassword 24 | ) 25 | 26 | function Install-App($clientContext, $appPackage, $productId) { 27 | $appName = [System.IO.Path]::GetFileNameWithoutExtension($appPackage) 28 | $web = $clientContext.Web 29 | 30 | Write-Verbose "Start to install app $appName..." 31 | 32 | # Try to uninstall any existing app instances first. 33 | Uninstall-App $clientContext $productId 34 | 35 | Write-Verbose "Installing app $appName..." 36 | $appInstance = $web.LoadAndInstallAppInSpecifiedLocale(([System.IO.FileInfo]$appPackage).OpenRead(), $web.Language) 37 | $clientContext.Load($appInstance) 38 | $clientContext.ExecuteQuery() 39 | 40 | $appInstance = WaitForAppOperationComplete $clientContext $appInstance.Id 41 | 42 | if (!$appInstance -Or $appInstance.Status -ne [Microsoft.SharePoint.Client.AppInstanceStatus]::Installed) 43 | { 44 | if ($appInstance -And $appInstance.Id) 45 | { 46 | Write-Error "App installation failed. To check app details, go to '$($web.Url.TrimEnd('/'))/_layouts/15/AppMonitoringDetails.aspx?AppInstanceId=$($appInstance.Id)'." 47 | } 48 | 49 | throw "App installation failed." 50 | } 51 | 52 | return $appInstance.Id 53 | } 54 | 55 | function Uninstall-App($clientContext, $productId) { 56 | $appInstances = $web.GetAppInstancesByProductId($productId) 57 | $clientContext.Load($appInstances) 58 | $clientContext.ExecuteQuery() 59 | 60 | if ($appInstances -And $appInstances.Length -gt 0) 61 | { 62 | $appInstance = $appInstances[0] 63 | 64 | Write-Verbose "Uninstalling app with instance id $($appInstance.Id)..." 65 | $appInstance.Uninstall() | out-null 66 | $clientContext.Load($appInstance) 67 | $clientContext.ExecuteQuery() 68 | 69 | $appInstance = WaitForAppOperationComplete $clientContext $appInstance.Id 70 | 71 | # Assume the app uninstallation succeeded 72 | Write-Verbose "App was uninstalled successfully." 73 | } 74 | } 75 | 76 | function WaitForAppOperationComplete($clientContext, $appInstanceId) { 77 | 78 | for ($i = 0; $i -le 2000; $i++) 79 | { 80 | try 81 | { 82 | $web = $clientContext.Web 83 | $instance = $web.GetAppInstanceById($appInstanceId) 84 | $clientContext.Load($instance) 85 | $clientContext.ExecuteQuery() 86 | } 87 | catch [Microsoft.SharePoint.Client.ServerException] 88 | { 89 | # When the uninstall finished, "app is not found" server exception will be thrown. 90 | # Assume the uninstalling operation succeeded. 91 | break 92 | } 93 | 94 | if (!$instance) 95 | { 96 | break 97 | } 98 | 99 | $result = $instance.Status; 100 | if ($result -ne [Microsoft.SharePoint.Client.AppInstanceStatus]::Installed -And 101 | !$instance.InError -And 102 | # If an app has failed to install correctly, it would return to initialized state if auto-cancel was enabled 103 | $result -ne [Microsoft.SharePoint.Client.AppInstanceStatus]::Initialized) 104 | { 105 | Write-Verbose "Instance status: $result" 106 | Start-Sleep -m 1000 107 | } 108 | else 109 | { 110 | break 111 | } 112 | } 113 | 114 | return $instance; 115 | } 116 | 117 | function Enable-SideLoading { 118 | 119 | param($clientContext, $enable = $true, $force = $false) 120 | 121 | # this is the side-loading Feature ID.. 122 | $FeatureId = [GUID]("AE3A1339-61F5-4f8f-81A7-ABD2DA956A7D") 123 | 124 | # ..and this one is site-scoped, so using $clientContext.Site.Features.. 125 | $siteFeatures = $clientContext.Site.Features 126 | $clientContext.Load($siteFeatures) 127 | $clientContext.ExecuteQuery() 128 | 129 | $feature = $siteFeatures | Where-Object { $_.DefinitionId -eq $FeatureId } | Select-Object -First 1 130 | 131 | if ($feature) 132 | { 133 | if ($enable) 134 | { 135 | Write-Verbose "Feature is already activated in this site." 136 | return 137 | } 138 | else 139 | { 140 | $siteFeatures.Remove($featureId, $force) 141 | } 142 | } 143 | else 144 | { 145 | if ($enable) 146 | { 147 | $siteFeatures.Add($featureId, $force, [Microsoft.SharePoint.Client.FeatureDefinitionScope]::None) 148 | } 149 | else 150 | { 151 | Write-Verbose "The feature is not active at this scope." 152 | return 153 | } 154 | } 155 | 156 | try 157 | { 158 | $clientContext.ExecuteQuery() 159 | if ($enable) 160 | { 161 | Write-Verbose "Feature '$FeatureId' successfully activated.." 162 | } 163 | else 164 | { 165 | Write-Verbose "Feature '$FeatureId' successfully deactivated.." 166 | } 167 | } 168 | catch 169 | { 170 | throw "An error occurred whilst activating/deactivating the Feature. Error detail: $($_)" 171 | } 172 | } 173 | 174 | function Load-Assemblies { 175 | 176 | # suppress output 177 | $assembly1 = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client") 178 | $assembly2 = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime") 179 | 180 | Write-Verbose "Assemblies loaded." 181 | } 182 | 183 | function WaitFor-IEReady { 184 | 185 | [CmdletBinding()] 186 | Param 187 | ( 188 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] 189 | $ie, 190 | 191 | [Parameter(Mandatory=$false, Position=1)] 192 | $initialWaitInSeconds = 1 193 | ) 194 | 195 | sleep -Seconds $initialWaitInSeconds 196 | 197 | while ($ie.Busy) { 198 | 199 | sleep -milliseconds 50 200 | } 201 | } 202 | 203 | <# 204 | .Synopsis 205 | Invoke a java script function. 206 | .DESCRIPTION 207 | Use this function to run JavaScript on a web page. Your $Command can 208 | return a value which will be returned by this function unless $global 209 | switch is specified in which case $Command will be executed in global 210 | scope and cannot return a value. If you received error 80020101 it means 211 | you need to fix your JavaScript code. 212 | .EXAMPLE 213 | Invoke-JavaScript -IE $ie -Command 'Post.IsSubmitReady();setTimeout(function() {Post.SubmitCreds(); }, 1000);' 214 | .EXAMPLE 215 | $result = Invoke-JavaScript -IE $ie -Command 'Post.IsSubmitReady();setTimeout(function() {Post.SubmitCreds(); }, 1000);' -Global 216 | #> 217 | function Invoke-JavaScript { 218 | 219 | [CmdletBinding()] 220 | [OutputType([string])] 221 | Param 222 | ( 223 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] 224 | $IE, 225 | 226 | [Parameter(Mandatory=$true, Position=1)] 227 | $Command, 228 | 229 | [switch]$Global 230 | ) 231 | 232 | if (-not $Global.IsPresent) { 233 | $Command = "document.body.setAttribute('PSResult', (function(){ $Command })());" 234 | } 235 | 236 | $document = $IE.document 237 | $window = $document.parentWindow 238 | $window.execScript($Command, 'javascript') | Out-Null 239 | 240 | if (-not $Global.IsPresent) { 241 | return $document.body.getAttribute('PSResult') 242 | } 243 | } 244 | 245 | function Get-AppPackageInformations { 246 | 247 | [CmdletBinding()] 248 | [OutputType([Hashtable])] 249 | param([string]$Path, [string]$AppWebUrl) 250 | 251 | # Open zip 252 | Add-Type -assembly System.IO.Compression.FileSystem 253 | Write-Verbose "Open zip file '$Path'..." 254 | $zip = [System.IO.Compression.ZipFile]::Open($Path, "Update") 255 | 256 | try{ 257 | $fileToEdit = "AppManifest.xml" 258 | $file = $zip.Entries.Where({$_.name -eq $fileToEdit}) 259 | 260 | Write-Verbose "Read app manifest from '$file'." 261 | $desiredFile = [System.IO.StreamReader]($file).Open() 262 | [xml]$xml = $desiredFile.ReadToEnd() 263 | $desiredFile.Close() 264 | 265 | if ($env:ClientId){ 266 | $clientId = $env:ClientId 267 | }else{ 268 | $clientId = $xml.App.AppPrincipal.RemoteWebApplication.ClientId 269 | } 270 | 271 | $appInformations = @{ 272 | ClientId = $clientId 273 | Version = $xml.App.Version 274 | AllowAppOnlyPolicy = [bool]$xml.App.AppPermissionRequests.AllowAppOnlyPolicy 275 | Title = $xml.App.Properties.Title 276 | ProductID = $xml.App.ProductID 277 | } 278 | 279 | if ($AppWebUrl){ 280 | # Replace URL 281 | $value = $xml.App.Properties.StartPage 282 | Write-Verbose "Replace URL in '$value' with '$AppWebUrl'." 283 | $value = $value -replace "^.*\?","$($AppWebUrl)?" 284 | $xml.App.Properties.StartPage = $value 285 | 286 | if ($env:ClientId){ 287 | Write-Verbose "Found ClientId '$env:ClientId' in environment. Replace it in app." 288 | $xml.App.AppPrincipal.RemoteWebApplication.ClientId = $env:ClientId 289 | } 290 | 291 | # Save file 292 | Write-Verbose "Save manifest to '$file'." 293 | $desiredFile = [System.IO.Stream]($file).Open() 294 | $desiredFile.SetLength(0) 295 | $xml.Save($desiredFile) 296 | $desiredFile.Flush() 297 | $desiredFile.Close() 298 | $desiredFile.Dispose() 299 | 300 | $desiredFile = [System.IO.StreamReader]($file).Open() 301 | [xml]$xml = $desiredFile.ReadToEnd() 302 | Write-Host $xml 303 | $desiredFile.Close() 304 | $desiredFile.Dispose() 305 | } 306 | 307 | return $appInformations 308 | 309 | }finally{ 310 | # Write the changes and close the zip file 311 | $zip.Dispose() 312 | } 313 | } 314 | 315 | function WaitFor-IEReady { 316 | 317 | [CmdletBinding()] 318 | Param 319 | ( 320 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] 321 | $ie, 322 | 323 | [Parameter(Mandatory=$false, Position=1)] 324 | $initialWaitInSeconds = 1 325 | ) 326 | 327 | sleep -Seconds $initialWaitInSeconds 328 | 329 | while ($ie.Busy) { 330 | 331 | sleep -milliseconds 50 332 | } 333 | } 334 | 335 | <# 336 | .Synopsis 337 | Invoke a java script function. 338 | .DESCRIPTION 339 | Use this function to run JavaScript on a web page. Your $Command can 340 | return a value which will be returned by this function unless $global 341 | switch is specified in which case $Command will be executed in global 342 | scope and cannot return a value. If you received error 80020101 it means 343 | you need to fix your JavaScript code. 344 | .EXAMPLE 345 | Invoke-JavaScript -IE $ie -Command 'Post.IsSubmitReady();setTimeout(function() {Post.SubmitCreds(); }, 1000);' 346 | .EXAMPLE 347 | $result = Invoke-JavaScript -IE $ie -Command 'Post.IsSubmitReady();setTimeout(function() {Post.SubmitCreds(); }, 1000);' -Global 348 | #> 349 | function Invoke-JavaScript { 350 | 351 | [CmdletBinding()] 352 | [OutputType([string])] 353 | Param 354 | ( 355 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] 356 | $IE, 357 | 358 | [Parameter(Mandatory=$true, Position=1)] 359 | $Command, 360 | 361 | [switch]$Global 362 | ) 363 | 364 | if (-not $Global.IsPresent) { 365 | $Command = "document.body.setAttribute('PSResult', (function(){ $Command })());" 366 | } 367 | 368 | $document = $IE.document 369 | $window = $document.parentWindow 370 | $window.execScript($Command, 'javascript') | Out-Null 371 | 372 | if (-not $Global.IsPresent) { 373 | return $document.body.getAttribute('PSResult') 374 | } 375 | } 376 | 377 | function Trust-SPAddIn { 378 | 379 | [CmdletBinding(SupportsShouldProcess=$true)] 380 | [OutputType([int])] 381 | Param 382 | ( 383 | [Parameter(Mandatory=$true, Position=0)] 384 | [guid]$AppInstanceId, 385 | 386 | [Parameter(Mandatory=$true, Position=1)] 387 | [string]$WebUrl, 388 | 389 | [parameter(Mandatory=$true, Position=2)] 390 | [string]$UserName, 391 | 392 | [parameter(Mandatory=$true, Position=3)] 393 | [string]$Password 394 | ) 395 | 396 | $authorizeURL = "$($WebUrl.TrimEnd('/'))/_layouts/15/appinv.aspx?AppInstanceId={$AppInstanceId}" 397 | 398 | $ie = New-Object -com internetexplorer.application 399 | 400 | try 401 | { 402 | $ie.Visible = $false 403 | $ie.Navigate2($authorizeURL) 404 | 405 | WaitFor-IEReady $ie 406 | 407 | Write-Verbose $ie.Document.Title -Verbose 408 | 409 | if ($ie.Document.Title -match "Sign in to Office 365.*") { 410 | 411 | Write-Verbose "Authenticate $UserName to O365..." 412 | # Authorize against O365 413 | $useAnotherLink = $ie.Document.getElementById("use_another_account_link") 414 | if ($useAnotherLink) { 415 | 416 | WaitFor-IEReady $ie 417 | 418 | $useAnotherLink.Click() 419 | 420 | WaitFor-IEReady $ie 421 | 422 | } 423 | 424 | $credUseridInputtext = $ie.Document.getElementById("cred_userid_inputtext") 425 | $credUseridInputtext.value = $UserName 426 | 427 | $credPasswordInputtext = $ie.Document.getElementById("cred_password_inputtext") 428 | $credPasswordInputtext.value = $Password 429 | 430 | WaitFor-IEReady $ie 431 | 432 | 433 | # make a jQuery call 434 | $result = Invoke-JavaScript -IE $ie -Command "`nPost.IsSubmitReady();`nsetTimeout(function() {`nPost.SubmitCreds();`n}, 1000);" 435 | 436 | WaitFor-IEReady $ie -initialWaitInSeconds 5 437 | 438 | } 439 | 440 | Write-Verbose $ie.Document.Title -Verbose 441 | 442 | 443 | if ($ie.Document.Title -match "Do you trust.*") 444 | { 445 | sleep -seconds 5 446 | 447 | 448 | $button = $ie.Document.getElementById("ctl00_PlaceHolderMain_BtnAllow") 449 | 450 | if ($button -eq $null) { 451 | $button = $ie.Document.getElementById("ctl00_PlaceHolderMain_LnkRetrust") 452 | } 453 | 454 | if ($button -eq $null) { 455 | 456 | throw "Could not find button to press" 457 | 458 | }else{ 459 | 460 | $button.click() 461 | 462 | WaitFor-IEReady $ie 463 | 464 | #if the button press was successful, we should now be on the Site Settings page.. 465 | if ($ie.Document.title -like "*trust*") { 466 | 467 | throw "Error: $($ie.Document.body.getElementsByClassName("ms-error").item().InnerText)" 468 | 469 | }else{ 470 | 471 | Write-Verbose "App was trusted successfully!" 472 | } 473 | } 474 | 475 | }else{ 476 | 477 | throw "Unexpected page '$($ie.LocationName)' was loaded. Please check your url." 478 | } 479 | } 480 | finally 481 | { 482 | $ie.Quit() 483 | } 484 | } 485 | 486 | Write-Host "Look for '$AppName' in '$applicationPath'..." 487 | $appPackage = Get-ChildItem -Path $applicationPath -Recurse -Include $AppName 488 | 489 | if (-not($appPackage)){ throw "No ap ppackage '$AppName' found in '$applicationPath'..." } 490 | 491 | Write-Host "Get package informations and set url in package to '$RemoteAppUrl'" 492 | $appPackageInformations = Get-AppPackageInformations -Path $appPackage.FullName -AppWebUrl $RemoteAppUrl 493 | 494 | 495 | Load-Assemblies 496 | 497 | Write-Host "Connect to '$WebUrl' as '$DeployUserName'..." 498 | $clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($webUrl) 499 | $clientContext.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($DeployUserName, (ConvertTo-SecureString $DeployPassword -AsPlainText -Force)) 500 | 501 | try 502 | { 503 | $web = $clientContext.Web 504 | $clientContext.Load($web) 505 | 506 | $clientContext.ExecuteQuery(); 507 | 508 | Write-Host "Successfully connected to '$WebUrl'..." 509 | 510 | Write-Host "Update SharePoint app '$AppName' in '$WebUrl' to Version '$($appPackageInformations.Version)'..." 511 | $appId = Install-App -clientContext $clientContext -appPackage $appPackage.FullName -productId $appPackageInformations.ProductID 512 | Write-Host "Done." 513 | 514 | Write-Host "Trust the app." 515 | Trust-SPAddIn -AppInstanceId $appId -WebUrl $WebUrl -UserName $DeployUserName -Password $DeployPassword 516 | } 517 | finally 518 | { 519 | $clientContext.Dispose() 520 | } -------------------------------------------------------------------------------- /Apps/Apps/Deploy-WebSite.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param 3 | ( 4 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, ValueFromPipeline=$true, Position=0)] 5 | [ValidateNotNullOrEmpty()] 6 | [string]$RootFolder, 7 | 8 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, ValueFromPipeline=$true, Position=1)] 9 | [ValidateNotNullOrEmpty()] 10 | [string]$DeployScript 11 | ) 12 | 13 | 14 | Write-Verbose "Look for batch '$DeployScript' in '$RootFolder'..." 15 | 16 | $files = Get-ChildItem -Path $RootFolder -Recurse -Filter $DeployScript 17 | 18 | if (-not ($files)) { throw "'$DeployScript' could not be found in '$RootFolder'."} 19 | 20 | if ($files.Count -gt 1) { throw "Found more then one deployment script." } 21 | 22 | $file = $files[0] 23 | 24 | Write-Verbose "Execute '$($file.FullName) /Y /M:https://$($env:WebDeploySiteName).scm.azurewebsites.net:443/msdeploy.axd /u:$($env:AzureUserName) /p:$($env:AzurePassword) /a:Basic'..." 25 | 26 | & $file /Y /M:https://$($env:WebDeploySiteName).scm.azurewebsites.net:443/msdeploy.axd /u:$($env:AzureUserName) /p:$($env:AzurePassword) /a:Basic 27 | 28 | 29 | Write-Verbose "Done." 30 | 31 | -------------------------------------------------------------------------------- /Apps/Apps/Install-SPAddIn/Install-SPAddIn.Tests.ps1: -------------------------------------------------------------------------------- 1 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".") 3 | . "$here\$sut" -AppPackageFullName "lala" -TargetWebFullUrl "http://localhost" 4 | 5 | Describe "Install-SPAddIn" { 6 | context "Given the app package does not exist, it" { 7 | It "throws an excpetion" { 8 | { Install-SPAddIn -AppPackageFullName "C:\lala.app" -TargetWebFullUrl http://localhost } | Should throw "The package 'C:\lala.app' does not exit." 9 | } 10 | } 11 | 12 | context "Given the app package does exist, it" { 13 | In $TestDrive { 14 | Setup -File ".\lala.app" 15 | Mock Install-SPAddInInternal { } -Verifiable 16 | 17 | It "does not throw an exception, and" { 18 | { Install-SPAddIn -AppPackageFullName ".\lala.app" -TargetWebFullUrl http://localhost } | Should not throw 19 | } 20 | 21 | It "installes the package in all sites."{ 22 | Install-SPAddIn -AppPackageFullName ".\lala.app" -TargetWebFullUrl @("http://localhost/sites/a", "http://localhost/sites/b") 23 | 24 | Assert-MockCalled Install-SPAddInInternal -Times 2 25 | } 26 | } 27 | } 28 | } 29 | 30 | Describe "Install-SPAddInInternal"{ 31 | context "Given the app cannot be installed, it" { 32 | Mock WaitFor-InstallJob { return 'Error' } 33 | 34 | $actual = Install-SPAddInInternal -AppPackageFullName "C:\lala.app" -webUrl http://localhost -sourceApp "ObjectModel" -Whatif -erroraction silentlycontinue 35 | 36 | It "does return one."{ 37 | $actual | should be 1 38 | } 39 | } 40 | 41 | context "Given the app is installed, it" { 42 | Mock WaitFor-InstallJob { return 'Installed' } 43 | 44 | $actual = Install-SPAddInInternal -AppPackageFullName "C:\lala.app" -webUrl http://localhost -sourceApp "ObjectModel" -Whatif 45 | 46 | It "returns zero."{ 47 | $actual | should be 0 48 | } 49 | } 50 | } 51 | 52 | Describe "WaitFor-InstallJob" { 53 | 54 | Mock Get-ParentSite { } 55 | 56 | context "Given the package cannot be installed, it"{ 57 | 58 | Mock Get-AppInstance { return [PSCustomObject]@{ Status = 'Error' } } 59 | 60 | $result = WaitFor-InstallJob -AppInstanceId ([guid]::NewGuid()) -WebUrl "http://localhost" 61 | 62 | It "returns the state returned from SharePoint." { 63 | $result | Should be "Error" 64 | } 65 | } 66 | 67 | context "Given the package cannot be installed in timeout period, it"{ 68 | 69 | Mock Get-AppInstance { return [PSCustomObject]@{ Status = 'Installing' } } -Verifiable 70 | 71 | $result = WaitFor-InstallJob -AppInstanceId ([guid]::NewGuid()) -WebUrl "http://localhost" 72 | 73 | It "requeries the status 5 times, and" { 74 | Assert-MockCalled Get-AppInstance -Times 5 75 | } 76 | 77 | It "returns the state from SharePoint." { 78 | $result | Should be "Installing" 79 | } 80 | } 81 | 82 | context "Given the package was installed immediattly, it"{ 83 | 84 | Mock Get-AppInstance { return [PSCustomObject]@{ Status = 'Installed' } } -Verifiable 85 | 86 | $result = WaitFor-InstallJob -AppInstanceId ([guid]::NewGuid()) -WebUrl "http://localhost" 87 | 88 | It "queries the app 1 time, and" { 89 | Assert-MockCalled Get-AppInstance -Times 1 90 | } 91 | 92 | It "returns the state 'Installed'." { 93 | $result | Should be "Installed" 94 | } 95 | } 96 | 97 | 98 | context "Given the package was installed after 6 seconds, it" { 99 | 100 | $Global:iteration = 0 101 | 102 | Mock Get-AppInstance { 103 | $Global:iteration++ 104 | if ($Global:iteration -lt 3) { 105 | return [PSCustomObject]@{ Status = 'Installing' } 106 | }else{ 107 | return [PSCustomObject]@{ Status = 'Installed' } 108 | } } -Verifiable 109 | 110 | $result = WaitFor-InstallJob -AppInstanceId ([guid]::NewGuid()) -WebUrl "http://localhost" 111 | 112 | It "queries the app 3 times, and" { 113 | Assert-MockCalled Get-AppInstance -Times 3 114 | } 115 | 116 | It "returns the state 'Installed'." { 117 | $result | Should be "Installed" 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Apps/Apps/Install-SPAddIn/Install-SPAddIn.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Installs a SharePoint AddIn A.K.A. SharePoint App to one or more specific sites. 4 | .DESCRIPTION 5 | This function installs a SharePoint AddIn A.K.A. SharePoint App to one or more specific sites. 6 | .EXAMPLE 7 | Install-SPAddIn -AppPackageFullName C:\MyCustomAppPackage.app -TargetWebFullUrl http://localhost 8 | .EXAMPLE 9 | Install-SPAddIn -AppPackageFullName C:\MyCustomAppPackage.app -TargetWebFullUrl @("http://localhost/sites/a", "http://localhost/sites/b") 10 | #> 11 | [CmdletBinding(SupportsShouldProcess=$true)] 12 | Param 13 | ( 14 | # The full name of the app package (i.e. 'C:\temp\MyCustomAppPackage.app') 15 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] 16 | [string]$AppPackageFullName, 17 | 18 | # The urls of the webs youwant to deploy the app to. 19 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=1)] 20 | [string[]]$TargetWebFullUrl, 21 | 22 | # The package source 23 | [Parameter(Mandatory=$false, Position=2)] 24 | [ValidateSet("ObjectModel", "Marketplace", "CorporateCatalog", "DeveloperSite", "RemoteObjectModel")] 25 | [string]$PackageSource = "ObjectModel" 26 | ) 27 | 28 | <# 29 | .Synopsis 30 | Installs a SharePoint AddIn A.K.A. SharePoint App to one or more specific sites. 31 | .DESCRIPTION 32 | This function installs a SharePoint AddIn A.K.A. SharePoint App to one or more specific sites. 33 | .EXAMPLE 34 | Install-SPAddIn -AppPackageFullName C:\MyCustomAppPackage.app -TargetWebFullUrl http://localhost 35 | .EXAMPLE 36 | Install-SPAddIn -AppPackageFullName C:\MyCustomAppPackage.app -TargetWebFullUrl @("http://localhost/sites/a", "http://localhost/sites/b") 37 | #> 38 | function Install-SPAddIn 39 | { 40 | [CmdletBinding(SupportsShouldProcess=$true)] 41 | Param 42 | ( 43 | # The full name of the app package (i.e. 'C:\temp\MyCustomAppPackage.app') 44 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] 45 | [string]$AppPackageFullName, 46 | 47 | # The urls of the webs youwant to deploy the app to. 48 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=1)] 49 | [string[]]$TargetWebFullUrl, 50 | 51 | # The package source 52 | [Parameter(Mandatory=$false, Position=2)] 53 | [ValidateSet("ObjectModel", "Marketplace", "CorporateCatalog", "DeveloperSite", "RemoteObjectModel")] 54 | [string]$PackageSource = "ObjectModel" 55 | ) 56 | 57 | Begin 58 | { 59 | if (-not (Test-Path $AppPackageFullName)) 60 | { 61 | throw "The package '$AppPackageFullName' does not exit." 62 | } 63 | 64 | Ensure-PSSnapin 65 | $sourceApp = [microsoft.sharepoint.administration.spappsource]$PackageSource 66 | } 67 | Process 68 | { 69 | foreach ($webUrl in $TargetWebFullUrl) 70 | { 71 | Install-SPAddInInternal -AppPackageFullName $AppPackageFullName -webUrl $webUrl -sourceApp $sourceApp 72 | } 73 | } 74 | End 75 | { 76 | Release-PSSnapin 77 | } 78 | } 79 | 80 | function Install-SPAddInInternal 81 | { 82 | [CmdletBinding(SupportsShouldProcess=$true)] 83 | [OutputType([int])] 84 | Param 85 | ( 86 | [Parameter(Mandatory=$true, Position=0)] 87 | [string]$AppPackageFullName, 88 | 89 | [Parameter(Mandatory=$true, Position=1)] 90 | [string]$webUrl, 91 | 92 | [Parameter(Mandatory=$true, Position=2)] 93 | [microsoft.sharepoint.administration.spappsource]$sourceApp 94 | ) 95 | 96 | Write-Verbose "Start to deploy '$AppPackageFullName' to '$webUrl'..." 97 | 98 | if ($PSCmdlet.ShouldProcess("$webUrl", "Import-SPAppPackage")) 99 | { 100 | $spapp = Import-SPAppPackage -Path "$AppPackageFullName" -Site $webUrl -Source $sourceApp -Confirm:$false -ErrorAction Stop 101 | } 102 | 103 | if ($PSCmdlet.ShouldProcess("$webUrl", "Trust-SPAddIn")) 104 | { 105 | $clientId = Get-ClientIdFromAppPackage $AppPackageFullName 106 | Trust-SPAddIn -ClientId $clientId -WebUrl $webUrl 107 | } 108 | 109 | $appId = [Guid]::Empty; 110 | 111 | if ($PSCmdlet.ShouldProcess("$webUrl", "Install-SPApp")) 112 | { 113 | $app = Install-SPApp -Web $WebUrl -Identity $spapp -Confirm:$false -ErrorAction Stop 114 | $appId = $app.Id 115 | } 116 | 117 | $status = WaitFor-InstallJob -AppInstanceId $appId -WebUrl $webUrl 118 | 119 | if ($status -ne [Microsoft.SharePoint.Administration.SPAppInstanceStatus]::Installed) 120 | { 121 | Write-Error "The app could not be installed. Current state is '$status'." 122 | 123 | $result = 1 124 | } 125 | else 126 | { 127 | $result = 0 128 | } 129 | 130 | Write-Verbose "End deploying '$AppPackageFullName' to '$webUrl'." 131 | 132 | return $result 133 | } 134 | 135 | function Trust-SPAddIn 136 | { 137 | [CmdletBinding(SupportsShouldProcess=$true)] 138 | [OutputType([int])] 139 | Param 140 | ( 141 | [Parameter(Mandatory=$true, Position=0)] 142 | [guid]$ClientID, 143 | 144 | [Parameter(Mandatory=$true, Position=1)] 145 | [string]$WebUrl 146 | ) 147 | 148 | $authRealm = Get-SPAuthenticationRealm -ServiceContext $WebUrl 149 | 150 | $appIdentifier = $clientID.ToString() + "@" + $authRealm 151 | 152 | $appPrincipal = Get-SPAppPrincipal -Site $WebUrl -NameIdentifier $appIdentifier 153 | 154 | Set-SPAppPrincipalPermission -Site $WebUrl -AppPrincipal $appPrincipal -Scope Site -Right FullControl 155 | } 156 | 157 | function WaitFor-InstallJob 158 | { 159 | [CmdletBinding()] 160 | param 161 | ( 162 | [guid]$AppInstanceId, 163 | 164 | [string]$WebUrl 165 | ) 166 | 167 | $site = Get-ParentSite -webUrl $WebUrl 168 | 169 | $AppInstance = Get-AppInstance -id $AppInstanceId -site $site 170 | 171 | Write-Verbose "Waiting for app..." 172 | 173 | $i = 0 174 | $max = 5 175 | $sleepTime = 2 176 | 177 | start-sleep -s $sleepTime 178 | 179 | while((($AppInstance.Status -eq [Microsoft.SharePoint.Administration.SPAppInstanceStatus]::Installing) -or ($AppInstance.Status -eq [Microsoft.SharePoint.Administration.SPAppInstanceStatus]::Initialized)) -and ($i -le $max)) 180 | { 181 | [int]$complete = ($i / $max) * 100 182 | Write-Progress -Activity "Install app..." -Status "$($complete)% completed" -PercentComplete $complete 183 | 184 | $i++ 185 | 186 | start-sleep -s $sleepTime 187 | 188 | $AppInstance = Get-AppInstance -id $AppInstanceId -site $site 189 | } 190 | 191 | Write-Progress -Activity "Install app" -Completed 192 | Write-Verbose "Result of installation is '$($AppInstance.Status)'." 193 | 194 | return $AppInstance.Status 195 | } 196 | 197 | function Get-AppInstance 198 | { 199 | param([guid]$id, $site) 200 | 201 | return Get-SPAppInstance -AppInstanceId $id -Site $site 202 | } 203 | 204 | function Get-ClientIdFromAppPackage 205 | { 206 | [CmdletBinding()] 207 | [OutputType([guid])] 208 | param([string]$Path) 209 | 210 | $folder = [System.IO.Path]::GetDirectoryName($Path) 211 | $tempFolder = "$folder\_tmp" 212 | 213 | mkdir (Join-Path $folder "_tmp") | Out-Null 214 | 215 | try{ 216 | 217 | $tempZipFile = "$tempFolder\app.zip" 218 | Copy-Item $Path $tempZipFile 219 | 220 | $shell = New-Object -Com Shell.Application 221 | $zipItem = $shell.NameSpace($tempZipFile) 222 | $appManifest = $zipItem.Items() | Where-Object -Property Name -EQ -Value AppManifest.xml 223 | $shell.NameSpace("$folder\_tmp").CopyHere($appManifest) 224 | 225 | [xml]$xml = Get-Content "$folder\_tmp\AppManifest.xml" 226 | 227 | return $xml.App.AppPrincipal.RemoteWebApplication.ClientId 228 | 229 | }finally{ 230 | Remove-Item "$folder\_tmp" -Force -Recurse 231 | } 232 | } 233 | 234 | function Get-ParentSite 235 | { 236 | param([string]$webUrl) 237 | 238 | return (Get-SPWeb $WebUrl).Site 239 | } 240 | 241 | function Write-Message 242 | { 243 | param([string]$message) 244 | 245 | Write-Verbose $message 246 | } 247 | 248 | function Ensure-PSSnapin 249 | { 250 | if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) 251 | { 252 | Add-PSSnapin "Microsoft.SharePoint.PowerShell" -Verbose:$false | Out-Null 253 | Write-Verbose "SharePoint Powershell Snapin loaded." 254 | } 255 | } 256 | 257 | function Release-PSSnapin 258 | { 259 | if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -ne $null) 260 | { 261 | Remove-PSSnapin "Microsoft.SharePoint.PowerShell" -Verbose:$false | Out-Null 262 | Write-Verbose "SharePoint Powershell Snapin removed." 263 | } 264 | } 265 | 266 | if (-not $MyInvocation.Line.Contains("`$here\`$sut")){ 267 | Install-SPAddIn -AppPackageFullName $AppPackageFullName -TargetWebFullUrl $TargetWebFullUrl -PackageSource $PackageSource 268 | } -------------------------------------------------------------------------------- /Apps/Apps/New-AppPackage/New-AppPackage.Tests.ps1: -------------------------------------------------------------------------------- 1 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".") 3 | . "$here\$sut" 4 | 5 | Describe "New-AppPackage" { 6 | context "Given the SourceDirectory does not exit, it"{ 7 | It "throws an exception"{ 8 | { New-AppPackage -SourceDirectory "C:\XYZ" } | Should throw 9 | } 10 | } 11 | 12 | context "Given the Binary directory does not exit, it"{ 13 | setup -Dir "SourceDir" 14 | 15 | It "creates the directory"{ 16 | $actual = New-AppPackage -SourceDirectory "$TestDrive\SourceDir" -BinariesDirectory "$TestDrive\XYZ" 17 | { Test-Path "$TestDrive\XYZ" } | Should Be $true 18 | } 19 | } 20 | 21 | context "Given a single app, it"{ 22 | setup -Dir "SourceDir" 23 | setup -Dir "BinariesDir" 24 | setup -File "SourceDir\app.publish\0.0.1.6\MySample.app" 25 | 26 | $actual = New-AppPackage -SourceDirectory "$TestDrive\SourceDir" -BinariesDirectory "$TestDrive\BinariesDir" 27 | 28 | It "copies exactly one app"{ 29 | $actual | should be 1 30 | } 31 | 32 | It "copies the app to the binaries directory"{ 33 | { Test-Path "$TestDrive\BinariesDir\MySample.app" } | Should Be $true 34 | } 35 | } 36 | 37 | context "Given two apps, it"{ 38 | setup -Dir "SourceDir" 39 | setup -Dir "BinariesDir" 40 | setup -File "SourceDir\app1\app.publish\0.0.1.6\MySample1.app" 41 | setup -File "SourceDir\app2\app.publish\0.0.1.6\MySample2.app" 42 | 43 | $actual = New-AppPackage -SourceDirectory "$TestDrive\SourceDir" -BinariesDirectory "$TestDrive\BinariesDir" 44 | 45 | It "copies both apps"{ 46 | $actual | should be 2 47 | } 48 | } 49 | 50 | context "Given an app in a different folders then 'app.publish', it"{ 51 | setup -Dir "SourceDir" 52 | setup -Dir "BinariesDir" 53 | setup -File "SourceDir\app1\xxxx\0.0.1.6\MySample1.app" 54 | 55 | $actual = New-AppPackage -SourceDirectory "$TestDrive\SourceDir" -BinariesDirectory "$TestDrive\BinariesDir" 56 | 57 | It "ignores the app"{ 58 | $actual | should be 0 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Apps/Apps/New-AppPackage/New-AppPackage.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Script that copies the app package to the drop folder. 4 | .DESCRIPTION 5 | This script van be used in a TFS build definition to ensure that an app is created and published to the build folder. 6 | You must use the script together with the build parameters /p:IsPackaging=True 7 | .EXAMPLE 8 | New-AppPackage -SourceDirectory $Env:TF_BUILD_SOURCESDIRECTORY -BinariesDirectory $Env:TF_BUILD_BINARIESDIRECTORY 9 | #> 10 | Param 11 | ( 12 | # The source directory 13 | [Parameter(Mandatory=$false, Position=0)] 14 | [string]$SourceDirectory = $Env:TF_BUILD_SOURCESDIRECTORY, 15 | 16 | #The binaries directory 17 | [Parameter(Mandatory=$false, Position=1)] 18 | [string]$BinariesDirectory = $Env:TF_BUILD_BINARIESDIRECTORY 19 | ) 20 | 21 | 22 | <# 23 | .Synopsis 24 | Script that copies the app package to the drop folder. 25 | .DESCRIPTION 26 | This script van be used in a TFS build definition to ensure that an app is created and published to the build folder. 27 | You must use the script together with the build parameters /p:IsPackaging=True 28 | .EXAMPLE 29 | New-AppPackage -SourceDirectory $Env:TF_BUILD_SOURCESDIRECTORY -BinariesDirectory $Env:TF_BUILD_BINARIESDIRECTORY 30 | #> 31 | function New-AppPackage 32 | { 33 | [CmdletBinding()] 34 | [OutputType([int])] 35 | Param 36 | ( 37 | # The source directory 38 | [Parameter(Mandatory=$false, Position=0)] 39 | [string]$SourceDirectory = $Env:TF_BUILD_SOURCESDIRECTORY, 40 | 41 | #The binaries directory 42 | [Parameter(Mandatory=$false, Position=1)] 43 | [string]$BinariesDirectory = $Env:TF_BUILD_BINARIESDIRECTORY 44 | ) 45 | 46 | Begin 47 | { 48 | if (-not (Test-Path $SourceDirectory)) { 49 | throw "The directory '$SourceDirectory' does not exist." 50 | } 51 | 52 | if (-not (Test-Path $BinariesDirectory)) { 53 | md $BinariesDirectory 54 | Write-Warning "The directory '$BinariesDirectory' did not exist and was created." 55 | } 56 | 57 | [int]$result = 0 58 | } 59 | Process 60 | { 61 | # Specify file types to include 62 | $FileTypes = $("*.app") 63 | 64 | # Specify the sub-folders to include 65 | $SourceSubFolders = $("*app.publish*") 66 | 67 | # Find the files 68 | $folders = Get-ChildItem $SourceDirectory -Recurse -Include $SourceSubFolders -Directory 69 | $files = $folders | foreach { Get-ChildItem -Path $_.FullName -Recurse -include $FileTypes } 70 | 71 | 72 | 73 | if($files) 74 | { 75 | Write-Verbose "Found $files.count files:" 76 | 77 | foreach ($file in $files) { 78 | Write-Verbose $file.FullName 79 | Copy-Item $file $BinariesDirectory 80 | $result++ 81 | } 82 | } 83 | else 84 | { 85 | Write-Warning "Found no files." 86 | } 87 | } 88 | End 89 | { 90 | return $result 91 | } 92 | } 93 | 94 | if (-not $MyInvocation.Line.Contains("`$here\`$sut")){ 95 | New-AppPackage -SourceDirectory $SourceDirectory -BinariesDirectory $BinariesDirectory 96 | } -------------------------------------------------------------------------------- /Apps/Apps/Replace-Tokens.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param 3 | ( 4 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, ValueFromPipeline=$true, Position=0)] 5 | [ValidateNotNullOrEmpty()] 6 | [string]$RootFolder, 7 | 8 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, ValueFromPipeline=$true, Position=1)] 9 | [ValidateNotNullOrEmpty()] 10 | [string]$FileName 11 | ) 12 | 13 | 14 | function Replace-Tokens 15 | { 16 | [CmdletBinding()] 17 | Param 18 | ( 19 | # Hilfebeschreibung zu Param1 20 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, ValueFromPipeline=$true, Position=0)] 21 | [ValidateNotNullOrEmpty()] 22 | [ValidateScript({ Test-Path $_ })] 23 | [string]$FileFullName 24 | ) 25 | 26 | Write-Verbose "Replace tokens in '$FileFullName'..." 27 | 28 | # get the environment variables 29 | $vars = Get-ChildItem -Path env:* 30 | 31 | # read in the setParameters file 32 | $contents = Get-Content -Path $FileFullName 33 | 34 | # perform a regex replacement 35 | $newContents = ""; 36 | $contents | ForEach-Object { 37 | 38 | $line = $_ 39 | if ($_ -match "__(\w+)__") { 40 | $setting = $vars | Where-Object { $_.Name -eq $Matches[1] } 41 | 42 | if ($setting) { 43 | Write-Verbose "Replacing key '$($setting.Name)' with value '$($setting.Value)' from environment" 44 | $line = $_ -replace "__(\w+)__", $setting.Value 45 | } 46 | } 47 | 48 | $newContents += $line + [Environment]::NewLine 49 | } 50 | 51 | Write-Verbose -Verbose "Save content to '$FileFullName'." 52 | Set-Content $FileFullName -Value $newContents 53 | 54 | Write-Verbose "Done" 55 | } 56 | 57 | Write-Verbose "Look for file '$FileName' in '$RootFolder'..." 58 | 59 | $files = Get-ChildItem -Path $RootFolder -Recurse -Filter $FileName 60 | 61 | Write-Verbose "Found $($files.Count) files." 62 | 63 | $files | ForEach-Object { Replace-Tokens -FileFullName $_.FullName } 64 | 65 | Write-Verbose "All files processed." 66 | 67 | -------------------------------------------------------------------------------- /Apps/Apps/Set-AssemblyVersion.Tests.ps1: -------------------------------------------------------------------------------- 1 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".") 3 | . "$here\$sut" 4 | 5 | 6 | Describe "Set-AssemblyVersion" { 7 | 8 | context "Given the SourceDirectory does not exit, it"{ 9 | 10 | It "throws an exception"{ 11 | { Set-AssemblyVersion -SourceDirectory "$TestDrive\XYZ" -BuildNumber "Dev_1.0.20150922.01" } | Should throw 12 | } 13 | } 14 | 15 | context "Given the SourceDirectory does exit, it"{ 16 | setup -File "SourceDir\Properties\AssemblyInfo.cs" -Content "[assembly: AssemblyVersion(""1.0.0.0"")]" 17 | 18 | It "does not throw an exception"{ 19 | { Set-AssemblyVersion -SourceDirectory "$TestDrive\SourceDir" -BuildNumber "Dev_1.0.20150922.01" } | Should not Throw 20 | } 21 | 22 | $content = Get-Content "$TestDrive\SourceDir\Properties\AssemblyInfo.cs" 23 | 24 | It "sets the version to the extracted verion of the build number"{ 25 | $content | should match "[assembly: AssemblyVersion(""1.0.20150922.01"")]" 26 | } 27 | } 28 | } 29 | 30 | Describe "Get-Version"{ 31 | 32 | context "Given a valid regex, it"{ 33 | 34 | It "returns the expected version if one match was found"{ 35 | $actual = Get-Version -BuildNumber "Build HelloWorld_0000.00.00.0" -VersionFormat "\d+\.\d+\.\d+\.\d+" 36 | 37 | $actual | Should be "0000.00.00.0" 38 | } 39 | 40 | It "returns the first result, if two results were found"{ 41 | $actual = Get-Version -BuildNumber "1111.00.00.0_Build HelloWorld_0000.00.00.0" -VersionFormat "\d+\.\d+\.\d+\.\d+" 42 | 43 | $actual | Should be "1111.00.00.0" 44 | } 45 | 46 | It "throws an exception, if no match could be found"{ 47 | 48 | { Get-Version -BuildNumber "Build HelloWorld_0" -VersionFormat "\d+\.\d+\.\d+\.\d+" } | Should throw 49 | } 50 | } 51 | 52 | context "Given an invalid regex, it"{ 53 | 54 | It "throws an exception."{ 55 | 56 | { Get-Version -BuildNumber "Build HelloWorld_0" -VersionFormat "lala" } | Should throw 57 | } 58 | } 59 | } 60 | 61 | Describe "Get-Files"{ 62 | 63 | context "Given two projects in a solution folder, it"{ 64 | 65 | setup -File "SourceDir\SolutionDir\Project1\Properties\AssemblyInfo.cs" 66 | setup -File "SourceDir\SolutionDir\Project2\Properties\AssemblyInfo.cs" 67 | 68 | $actual = Get-Files -SourceDir $TestDrive\SourceDir 69 | 70 | It "returns two Assembly.cs files"{ 71 | $actual | should not BeNullOrEmpty 72 | $actual.Count | should be 2 73 | } 74 | } 75 | 76 | context "Given two projects in different subfolders, it"{ 77 | 78 | setup -File "SourceDir\SolutionDir\Subfolder1\Project1\Properties\AssemblyInfo.cs" 79 | setup -File "SourceDir\SolutionDir\Sub1\Sub2\Project2\Properties\AssemblyInfo.cs" 80 | 81 | $actual = Get-Files -SourceDir $TestDrive\SourceDir 82 | 83 | It "returns two Assembly.cs files"{ 84 | $actual | should not BeNullOrEmpty 85 | $actual.Count | should be 2 86 | } 87 | } 88 | 89 | context "Given a visual basic project, it"{ 90 | 91 | setup -File "SourceDir\Properties\AssemblyInfo.vb" 92 | 93 | $actual = Get-Files -SourceDir $TestDrive\SourceDir 94 | 95 | It "returns two Assembly.cs files"{ 96 | $actual | should not BeNullOrEmpty 97 | $actual.Count | should be 1 98 | } 99 | } 100 | } 101 | 102 | Describe "Set-FileContent"{ 103 | 104 | context "Given an AssemblyInfo.cs file, it"{ 105 | 106 | setup -File "SourceDir\AssemblyInfo.cs" -Content "//comment`r`n[assembly: AssemblyVersion(""1.0.0.0"")]`r`n[assembly: AssemblyFileVersion(""1.0.0.0"")]`r`n" 107 | 108 | Set-FileContent (gci "$TestDrive\SourceDir") "15.88.99.17" "\d+\.\d+\.\d+\.\d+" 109 | 110 | $content = Get-Content "$TestDrive\SourceDir\AssemblyInfo.cs" 111 | 112 | It "sets the Version to the given version"{ 113 | $content[1] | should match "[assembly: AssemblyVersion(""15.88.99.1"")]" 114 | } 115 | 116 | It "sets the FileVersion to the given version"{ 117 | $content[2] | should match "[assembly: AssemblyFileVersion(""15.88.99.1"")]" 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /Apps/Apps/Set-AssemblyVersion.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Sets the assembly version of all assemblies in the source directory. 4 | .DESCRIPTION 5 | A build script that can be included in TFS 2015 or Visual Studio Online (VSO) vNevt builds that update the version of all assemblies in a workspace. 6 | It uses the name of the build to extract the version number and updates all AssemblyInfo.cs files to use the new version. 7 | .EXAMPLE 8 | Set-AssemblyVersion 9 | .EXAMPLE 10 | Set-AssemblyVersion -SourceDirectory $Env:BUILD_SOURCESDIRECTORY -BuildNumber $Env:BUILD_BUILDNUMBER 11 | .EXAMPLE 12 | Set-AssemblyVersion -SourceDirectory ".\SourceDir" -BuildNumber "Dev_1.0.20150922.01" -VersionFormat "\d+\.\d+\.\d+\.\d+" 13 | #> 14 | 15 | [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] 16 | [Alias()] 17 | [OutputType([int])] 18 | Param 19 | ( 20 | # The path to the source directory. Default $Env:BUILD_SOURCESDIRECTORY is set by TFS. 21 | [Parameter(Mandatory=$false, Position=0)] 22 | [ValidateNotNullOrEmpty()] 23 | [string]$SourceDirectory = $Env:BUILD_SOURCESDIRECTORY, 24 | 25 | # The build number. Default $Env:BUILD_BUILDNUMBER is set by TFS and must be configured according your regex. 26 | [Parameter(Mandatory=$false, Position=1)] 27 | [ValidateNotNullOrEmpty()] 28 | [string]$BuildNumber = $Env:BUILD_BUILDNUMBER, 29 | 30 | # The build number. Default $Env:BUILD_BUILDNUMBER is set by TFS and must be configured according your regex. 31 | [Parameter(Mandatory=$false, Position=2)] 32 | [ValidateNotNullOrEmpty()] 33 | [string]$VersionFormat = "\d+\.\d+\.\d+\.\d+" 34 | ) 35 | 36 | <# 37 | .Synopsis 38 | Sets the assembly version of all assemblies in the source directory. 39 | .DESCRIPTION 40 | A build script that can be included in TFS 2015 or Visual Studio Online (VSO) vNevt builds that update the version of all assemblies in a workspace. 41 | It uses the name of the build to extract the version number and updates all AssemblyInfo.cs files to use the new version. 42 | .EXAMPLE 43 | Set-AssemblyVersion 44 | .EXAMPLE 45 | Set-AssemblyVersion -SourceDirectory $Env:BUILD_SOURCESDIRECTORY -BuildNumber $Env:BUILD_BUILDNUMBER 46 | .EXAMPLE 47 | Set-AssemblyVersion -SourceDirectory ".\SourceDir" -BuildNumber "Dev_1.0.20150922.01" -VersionFormat "\d+\.\d+\.\d+\.\d+" 48 | #> 49 | function Set-AssemblyVersion 50 | { 51 | [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] 52 | [Alias()] 53 | [OutputType([int])] 54 | Param 55 | ( 56 | # The path to the source directory. Default $Env:BUILD_SOURCESDIRECTORY is set by TFS. 57 | [Parameter(Mandatory=$false, Position=0)] 58 | [ValidateNotNullOrEmpty()] 59 | [string]$SourceDirectory = $Env:BUILD_SOURCESDIRECTORY, 60 | 61 | # The build number. Default $Env:BUILD_BUILDNUMBER is set by TFS and must be configured according your regex. 62 | [Parameter(Mandatory=$false, Position=1)] 63 | [ValidateNotNullOrEmpty()] 64 | [string]$BuildNumber = $Env:BUILD_BUILDNUMBER, 65 | 66 | # The build number. Default $Env:BUILD_BUILDNUMBER is set by TFS and must be configured according your regex. 67 | [Parameter(Mandatory=$false, Position=2)] 68 | [ValidateNotNullOrEmpty()] 69 | [string]$VersionFormat = "\d+\.\d+\.\d+\.\d+" 70 | ) 71 | 72 | if (-not (Test-Path $SourceDirectory)) { 73 | throw "The directory '$SourceDirectory' does not exist." 74 | } 75 | 76 | $Version = Get-Version -BuildNumber $BuildNumber -VersionFormat $VersionFormat 77 | 78 | $files = Get-Files -SourceDirectory $SourceDirectory 79 | 80 | Set-FileContent -Files $files -Version $Version -VersionFormat $VersionFormat 81 | } 82 | 83 | function Get-Version 84 | { 85 | [CmdletBinding()] 86 | [Alias()] 87 | [OutputType([string])] 88 | Param 89 | ( 90 | [Parameter(Mandatory=$true, Position=0)] 91 | [ValidateNotNullOrEmpty()] 92 | [string]$BuildNumber, 93 | 94 | [Parameter(Mandatory=$true, Position=1)] 95 | [ValidateNotNullOrEmpty()] 96 | [string]$VersionFormat 97 | ) 98 | 99 | $VersionData = [regex]::matches($BuildNumber,$VersionFormat) 100 | 101 | if ($VersionData.Count -eq 0){ 102 | throw "Could not find version number with format '$VersionFormat' in BUILD_BUILDNUMBER '$BuildNumber'." 103 | } 104 | 105 | return $VersionData[0] 106 | } 107 | 108 | function Get-Files 109 | { 110 | [CmdletBinding()] 111 | [Alias()] 112 | [OutputType([System.IO.FileSystemInfo[]])] 113 | Param 114 | ( 115 | [Parameter(Mandatory=$true, Position=0)] 116 | [ValidateNotNullOrEmpty()] 117 | [string]$SourceDirectory 118 | ) 119 | 120 | $folders = Get-ChildItem $SourceDirectory -Recurse -Include "*Properties*" | Where-Object { $_.PSIsContainer } 121 | 122 | return $folders | ForEach-Object { Get-ChildItem -Path $_.FullName -Recurse -include AssemblyInfo.* } 123 | } 124 | 125 | function Set-FileContent 126 | { 127 | [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] 128 | [OutputType([int])] 129 | Param 130 | ( 131 | [Parameter(Mandatory=$true, Position=0)] 132 | [System.IO.FileSystemInfo[]]$Files, 133 | 134 | [Parameter(Mandatory=$true, Position=1)] 135 | [string]$Version, 136 | 137 | [Parameter(Mandatory=$true, Position=2)] 138 | [string]$VersionFormat 139 | ) 140 | 141 | foreach ($file in $Files) 142 | { 143 | $filecontent = Get-Content $file 144 | 145 | if ($PSCmdlet.ShouldProcess("$file", "Set-AssemblyVersion")) 146 | { 147 | attrib $file -r 148 | $filecontent -replace $VersionFormat, $Version | Out-File $file 149 | Write-Verbose -Message "Applied Version '$Version' $($file.FullName) - version applied" 150 | } 151 | } 152 | } 153 | 154 | if ($myinvocation.line.Contains("`$here\`$sut")) { 155 | Write-Verbose -Message "Script was called ba pester: $($myinvocation.line)" 156 | 157 | }else{ 158 | 159 | Write-Verbose -Message "Script was called with the following arguments:" 160 | Write-Verbose -Message "SourceDirectory: $SourceDirectory" 161 | Write-Verbose -Message "BuildNumber: $BuildNumber" 162 | Write-Verbose -Message "VersionFormat: $VersionFormat" 163 | Set-AssemblyVersion -SourceDirectory $SourceDirectory -BuildNumber $BuildNumber -VersionFormat $VersionFormat 164 | } -------------------------------------------------------------------------------- /Apps/Apps/Trust-SPApp.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] 2 | [OutputType([int])] 3 | Param 4 | ( 5 | [Parameter(Mandatory=$true, Position=0)] 6 | [ValidateNotNullOrEmpty()] 7 | [string]$AppName, 8 | 9 | [Parameter(Mandatory=$true, Position=1)] 10 | [ValidateNotNullOrEmpty()] 11 | [string]$WebUrl, 12 | 13 | [Parameter(Mandatory=$true, Position=2)] 14 | [ValidateNotNullOrEmpty()] 15 | [string]$RemoteAppUrl, 16 | 17 | [Parameter(Mandatory=$true, Position=3)] 18 | [ValidateNotNullOrEmpty()] 19 | [string]$DeployUserName, 20 | 21 | [Parameter(Mandatory=$true, Position=4)] 22 | [ValidateNotNullOrEmpty()] 23 | [string]$DeployPassword 24 | ) 25 | 26 | function Load-Assemblies 27 | { 28 | # suppress output 29 | $assembly1 = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client") 30 | $assembly2 = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime") 31 | 32 | Write-Verbose "Assemblies loaded." 33 | } 34 | 35 | function WaitFor-IEReady 36 | { 37 | [CmdletBinding()] 38 | Param 39 | ( 40 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] 41 | $ie, 42 | 43 | [Parameter(Mandatory=$false, Position=1)] 44 | $initialWaitInSeconds = 1 45 | ) 46 | 47 | sleep -Seconds $initialWaitInSeconds 48 | 49 | while ($ie.Busy) { 50 | 51 | sleep -milliseconds 50 52 | } 53 | } 54 | 55 | <# 56 | .Synopsis 57 | Invoke a java script function. 58 | .DESCRIPTION 59 | Use this function to run JavaScript on a web page. Your $Command can 60 | return a value which will be returned by this function unless $global 61 | switch is specified in which case $Command will be executed in global 62 | scope and cannot return a value. If you received error 80020101 it means 63 | you need to fix your JavaScript code. 64 | .EXAMPLE 65 | Invoke-JavaScript -IE $ie -Command 'Post.IsSubmitReady();setTimeout(function() {Post.SubmitCreds(); }, 1000);' 66 | .EXAMPLE 67 | $result = Invoke-JavaScript -IE $ie -Command 'Post.IsSubmitReady();setTimeout(function() {Post.SubmitCreds(); }, 1000);' -Global 68 | #> 69 | function Invoke-JavaScript 70 | { 71 | [CmdletBinding()] 72 | [OutputType([string])] 73 | Param 74 | ( 75 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] 76 | $IE, 77 | 78 | [Parameter(Mandatory=$true, Position=1)] 79 | $Command, 80 | 81 | [switch]$Global 82 | ) 83 | 84 | if (-not $Global.IsPresent) { 85 | $Command = "document.body.setAttribute('PSResult', (function(){ $Command })());" 86 | } 87 | 88 | $document = $IE.document 89 | $window = $document.parentWindow 90 | $window.execScript($Command, 'javascript') | Out-Null 91 | 92 | if (-not $Global.IsPresent) { 93 | return $document.body.getAttribute('PSResult') 94 | } 95 | } 96 | 97 | function Trust-SPAddIn 98 | { 99 | [CmdletBinding(SupportsShouldProcess=$true)] 100 | [OutputType([int])] 101 | Param 102 | ( 103 | [Parameter(Mandatory=$true, Position=0)] 104 | [guid]$AppInstanceId, 105 | 106 | [Parameter(Mandatory=$true, Position=1)] 107 | [string]$WebUrl, 108 | 109 | [parameter(Mandatory=$true, Position=2)] 110 | [string]$UserName, 111 | 112 | [parameter(Mandatory=$true, Position=3)] 113 | [string]$Password 114 | ) 115 | 116 | $authorizeURL = "$($WebUrl.TrimEnd('/'))/_layouts/15/appinv.aspx?AppInstanceId={$AppInstanceId}" 117 | 118 | $ie = New-Object -com internetexplorer.application 119 | 120 | try 121 | { 122 | $ie.Visible = $false 123 | $ie.Navigate2($authorizeURL) 124 | 125 | WaitFor-IEReady $ie 126 | 127 | if ($ie.LocationName -eq "Sign in to Office 365"){ 128 | 129 | Write-Verbose "Authenticate $UserName to O365..." 130 | # Authorize against O365 131 | $useAnotherLink = $ie.Document.getElementById("use_another_account_link") 132 | if ($useAnotherLink) { 133 | 134 | WaitFor-IEReady $ie 135 | 136 | $useAnotherLink.Click() 137 | 138 | WaitFor-IEReady $ie 139 | 140 | } 141 | 142 | $credUseridInputtext = $ie.Document.getElementById("cred_userid_inputtext") 143 | $credUseridInputtext.value = $UserName 144 | 145 | $credPasswordInputtext = $ie.Document.getElementById("cred_password_inputtext") 146 | $credPasswordInputtext.value = $Password 147 | 148 | WaitFor-IEReady $ie 149 | 150 | 151 | # make a jQuery call 152 | $result = Invoke-JavaScript -IE $ie -Command "`nPost.IsSubmitReady();`nsetTimeout(function() {`nPost.SubmitCreds();`n}, 1000);" 153 | 154 | WaitFor-IEReady $ie -initialWaitInSeconds 5 155 | 156 | } 157 | 158 | Write-Verbose $ie.Document.Title 159 | 160 | 161 | if ($ie.LocationName -eq "Do you trust CalculatorApp-Dev?") 162 | { 163 | sleep -seconds 5 164 | 165 | $button = $ie.Document.getElementById("ctl00_PlaceHolderMain_BtnAllow") 166 | if ($button -eq $null) { 167 | 168 | throw "Could not find button to press" 169 | 170 | }else{ 171 | 172 | $button.click() 173 | 174 | WaitFor-IEReady $ie 175 | 176 | #if the button press was successful, we should now be on the Site Settings page.. 177 | if ($ie.Document.title -like "*trust*") { 178 | 179 | throw "Error: $($ie.Document.body.getElementsByClassName("ms-error").item().InnerText)" 180 | 181 | }else{ 182 | 183 | Write-Verbose "App was trusted successfully!" 184 | } 185 | } 186 | 187 | }else{ 188 | 189 | throw "Unexpected page '$($ie.LocationName)' was loaded. Please check your url." 190 | } 191 | } 192 | finally 193 | { 194 | $ie.Quit() 195 | } 196 | } 197 | 198 | 199 | function Get-AppPackageInformations{ 200 | 201 | [CmdletBinding()] 202 | [OutputType([Hashtable])] 203 | param([string]$Path, [string]$AppWebUrl) 204 | 205 | # Open zip 206 | Add-Type -assembly System.IO.Compression.FileSystem 207 | Write-Verbose "Open zip file '$Path'..." 208 | $zip = [System.IO.Compression.ZipFile]::Open($Path, "Update") 209 | 210 | try{ 211 | $fileToEdit = "AppManifest.xml" 212 | $file = $zip.Entries.Where({$_.name -eq $fileToEdit}) 213 | 214 | Write-Verbose "Read app manifest from '$file'." 215 | $desiredFile = [System.IO.StreamReader]($file).Open() 216 | [xml]$xml = $desiredFile.ReadToEnd() 217 | $desiredFile.Close() 218 | 219 | $appInformations = @{ 220 | ClientId = $xml.App.AppPrincipal.RemoteWebApplication.ClientId 221 | Version = $xml.App.Version 222 | AllowAppOnlyPolicy = [bool]$xml.App.AppPermissionRequests.AllowAppOnlyPolicy 223 | Title = $xml.App.Properties.Title 224 | ProductID = $xml.App.ProductID 225 | } 226 | 227 | if ($AppWebUrl){ 228 | # Replace URL 229 | $value = $xml.App.Properties.StartPage 230 | Write-Verbose "Replace URL in '$value' with '$AppWebUrl'." 231 | $value = $value -replace "^.*\?","$($AppWebUrl)?" 232 | $xml.App.Properties.StartPage = $value 233 | 234 | 235 | # Save file 236 | Write-Verbose "Save manifest to '$file'." 237 | $desiredFile = [System.IO.Stream]($file).Open() 238 | $desiredFile.SetLength(0) 239 | $xml.Save($desiredFile) 240 | $desiredFile.Flush() 241 | $desiredFile.Close() 242 | $desiredFile.Dispose() 243 | 244 | $desiredFile = [System.IO.StreamReader]($file).Open() 245 | [xml]$xml = $desiredFile.ReadToEnd() 246 | Write-Host $xml 247 | $desiredFile.Close() 248 | $desiredFile.Dispose() 249 | } 250 | 251 | return $appInformations 252 | 253 | }finally{ 254 | # Write the changes and close the zip file 255 | $zip.Dispose() 256 | } 257 | } 258 | 259 | Write-Host "Look for '$AppName' in '$applicationPath'..." 260 | $appPackage = Get-ChildItem -Path $applicationPath -Recurse -Include $AppName 261 | 262 | if (-not($appPackage)){ throw "No ap ppackage '$AppName' found in '$applicationPath'..." } 263 | 264 | Write-Host "Get package informations and set url in package to '$RemoteAppUrl'" 265 | $appPackageInformations = Get-AppPackageInformations -Path $appPackage.FullName -AppWebUrl $RemoteAppUrl 266 | 267 | 268 | Load-Assemblies 269 | 270 | Write-Host "Connect to '$WebUrl' as '$DeployUserName'..." 271 | $clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($webUrl) 272 | $clientContext.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($DeployUserName, (ConvertTo-SecureString $DeployPassword -AsPlainText -Force)) 273 | 274 | try 275 | { 276 | $web = $clientContext.Web 277 | $clientContext.Load($web) 278 | 279 | $clientContext.ExecuteQuery(); 280 | 281 | Write-Host "Successfully connected to '$WebUrl'..." 282 | 283 | Write-Host "Update SharePoint app '$AppName' in '$WebUrl' to Version '$($appPackageInformations.Version)'..." 284 | $appId = Install-App -clientContext $clientContext -appPackage $appPackage.FullName -productId $appPackageInformations.ProductID 285 | Write-Host "Done." 286 | 287 | Write-Host "Trust App..." 288 | Trust-SPAddIn -AppInstanceId $appId -WebUrl $WebUrl -UserName $DeployUserName -Password $DeployPassword 289 | Write-Host "Done." 290 | } 291 | finally 292 | { 293 | $clientContext.Dispose() 294 | } -------------------------------------------------------------------------------- /DSCPublishing/DSCResourcesConfiguration.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | # Ensure the current state of the DSC resource modules on the current server. 3 | # This file is optimized for release management. 4 | # Use Ensure-DSCResources.ps1 to manually deploy the resources. 5 | #> 6 | Configuration DSCResourceConfiguration 7 | { 8 | Node $env:COMPUTERNAME 9 | { 10 | File DSCModules 11 | { 12 | SourcePath = "$applicationPath\DSCResources" 13 | DestinationPath = "$env:ProgramFiles\WindowsPowerShell\Modules" 14 | Type = "Directory" 15 | Recurse = $true 16 | Ensure = "Present" 17 | } 18 | } 19 | } 20 | 21 | DSCResourceConfiguration -------------------------------------------------------------------------------- /DSCPublishing/Ensure-DSCResources.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | # Script to run the "DSCResourcesConfiguration.ps1" and start the DSC-Configuration. 3 | # Note: run this script in the root of the git repository (i.e. \Source\Repos\SPAdministration\DSCResources). 4 | #> 5 | $applicationPath = Resolve-Path . 6 | 7 | . .\DSCResourcesConfiguration.ps1 8 | 9 | Start-DscConfiguration DSCResourceConfiguration -Wait -Force -Verbose -------------------------------------------------------------------------------- /DSCPublishing/Package.ps1: -------------------------------------------------------------------------------- 1 | # Specify file types to exclude 2 | $FileTypes = $("*.log","*.proj","*.vsprops") 3 | 4 | $SourceDir = "$($Env:TF_BUILD_SOURCESDIRECTORY.TrimEnd('\'))\DSCResources" 5 | 6 | # Find the files 7 | $files = gci $SourceDir -Recurse -Exclude $FileTypes 8 | 9 | if($files) 10 | { 11 | Write-Verbose "Found $files.count files:" 12 | 13 | foreach ($file in $files) { 14 | Write-Verbose $file.FullName 15 | } 16 | } 17 | else 18 | { 19 | Write-Warning "Found no files." 20 | } 21 | 22 | # If binary output directory exists, make sure it is empty 23 | # If it does not exist, create one 24 | # (this happens when 'Clean workspace' build process parameter is set to True) 25 | if ([IO.Directory]::Exists($Env:TF_BUILD_BINARIESDIRECTORY)) 26 | { 27 | $DeletePath = $Env:TF_BUILD_BINARIESDIRECTORY + "\*" 28 | Write-Verbose "$Env:TF_BUILD_BINARIESDIRECTORY exists." 29 | if(-not $Disable) 30 | { 31 | Write-Verbose "Ready to delete $DeletePath" 32 | Remove-Item $DeletePath -recurse 33 | Write-Verbose "Files deleted." 34 | } 35 | } 36 | else 37 | { 38 | Write-Verbose "$Env:TF_BUILD_BINARIESDIRECTORY does not exist." 39 | 40 | if(-not $Disable) 41 | { 42 | Write-Verbose "Ready to create it." 43 | [IO.Directory]::CreateDirectory($Env:TF_BUILD_BINARIESDIRECTORY) | Out-Null 44 | Write-Verbose "Directory created." 45 | } 46 | } 47 | 48 | # Copy the binaries 49 | Write-Verbose "Ready to copy files." 50 | if(-not $Disable) 51 | { 52 | Copy $SourceDir $Env:TF_BUILD_BINARIESDIRECTORY -Recurse -Exclude $FileTypes 53 | Write-Verbose "Files copied." 54 | } 55 | -------------------------------------------------------------------------------- /DSCPublishing/README.md: -------------------------------------------------------------------------------- 1 | # DSC Publishing 2 | ## TFSBuild.proj and Package.ps1 3 | TFSBuild.proj is a simple project file that can be used to package files in a TFS Team build. 4 | The project file calls package.ps1 to copy all files to the drop folder. 5 | 6 | U can use this to package your dsc resources from a git repository using its own build. 7 | 8 | ## DSCResourcesConfiguration.ps1 and Ensure-DSCResources.ps1 9 | Use DSCResourcesConfiguration.ps1 to ensure the dsc resources on all machines before deploying any configurations in Releasemanagement. Use Ensure-DSCResources.ps1 to locally deploy the resources. -------------------------------------------------------------------------------- /DSCPublishing/TFSBuild.proj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /DevOps/DevOps.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F5034706-568F-408A-B7B3-4D38C6DB8A32}") = "DevOps", "DevOps\DevOps.pssproj", "{6CAFC0C6-A428-4D30-A9F9-700E829FEA51}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /DevOps/DevOps/Backup-Database.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Backup Database 4 | .DESCRIPTION 5 | make a full backup of a given database 6 | .EXAMPLE 7 | Backup-Database LSK -ServerInstance lissval-t02 8 | .EXAMPLE 9 | #> 10 | [CmdletBinding()] 11 | Param 12 | ( 13 | [Parameter(Mandatory=$true, 14 | ValueFromPipelineByPropertyName=$true, 15 | Position=0)] 16 | [string] 17 | $DataBaseName, 18 | 19 | [Parameter(Mandatory=$true, 20 | ValueFromPipelineByPropertyName=$true, 21 | Position=1)] 22 | [string] 23 | $ServerInstance 24 | ) 25 | 26 | Begin 27 | { 28 | } 29 | Process 30 | { 31 | Backup-SqlDatabase $DataBaseName -ServerInstance $ServerInstance 32 | } 33 | End 34 | { 35 | } -------------------------------------------------------------------------------- /DevOps/DevOps/Backup-Website.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Backup an existing Database 4 | .DESCRIPTION 5 | Moves or copies all the content of a website to a backup folder. 6 | .EXAMPLE 7 | Backup-Website -WebsiteName LSK.Portal 8 | #> 9 | [CmdletBinding()] 10 | Param 11 | ( 12 | [Parameter(Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)] 13 | [string] 14 | $WebSiteName, 15 | 16 | [string] 17 | $WebSitePath = "C:\inetpub\wwwroot\", 18 | 19 | [string] 20 | $BackupPath = "C:\Deployment\Backup\", 21 | 22 | [switch] 23 | $Clean 24 | ) 25 | 26 | Begin 27 | { 28 | $BackupFolder = Join-Path $BackupPath $WebSiteName 29 | $WebSiteFullName = Join-Path $WebSitePath $WebSiteName 30 | 31 | if (-not (Test-Path $WebSiteFullName)) 32 | { 33 | $message = "Website '$WebSiteFullName' does not exist." 34 | $exception = New-Object InvalidOperationException $message 35 | $errorID = 'FolderNotFound' 36 | $errorCategory = [Management.Automation.ErrorCategory]::InvalidArgument 37 | $target = $WebSiteName 38 | $errorRecord = New-Object Management.Automation.ErrorRecord $exception, $errorID, $errorCategory, $target 39 | $PSCmdlet.ThrowTerminatingError($errorRecord) 40 | } 41 | 42 | if (Test-Path $BackupFolder) 43 | { 44 | Write-Verbose "Cleaning up old backup in '$BackupFolder'..." 45 | Remove-Item $BackupFolder -Force -Recurse 46 | Write-Verbose "Done." 47 | } 48 | 49 | Write-Verbose "Create backup folder '$BackupFolder'..." 50 | md $BackupFolder | Out-Null 51 | Write-Verbose "Done." 52 | } 53 | 54 | Process 55 | { 56 | Write-Verbose "Backup site '$WebSiteFullName' to backup location '$BackupFolder'..." 57 | Get-ChildItem -Path $WebSiteFullName | % { 58 | if ($Clean.IsPresent) 59 | { 60 | Write-Verbose "Move '$_.FullName'..." 61 | Move-Item $_.FullName "$BackupFolder" -Force 62 | } 63 | else 64 | { 65 | Write-Verbose "Copy '$_.FullName'..." 66 | Copy-Item $_.FullName "$BackupFolder" -Force -Recurse 67 | } 68 | 69 | Write-Verbose "Done." 70 | } 71 | } 72 | 73 | End 74 | { 75 | } -------------------------------------------------------------------------------- /DevOps/DevOps/DevOps.pssproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | 2.0 6 | 6CAFC0C6-A428-4d30-A9F9-700E829FEA51 7 | Exe 8 | MyApplication 9 | MyApplication 10 | DevOps 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug\ 17 | DEBUG;TRACE 18 | prompt 19 | 4 20 | 21 | 22 | pdbonly 23 | true 24 | bin\Release\ 25 | TRACE 26 | prompt 27 | 4 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /DevOps/DevOps/Get-TFSRelease.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param ( 3 | [Parameter(Mandatory=$true, Position=0)] 4 | $ProjectCollection 5 | ) 6 | 7 | $projectsResult = Invoke-RestMethod "$ProjectCollection/_apis/projects?api-version=1.0" -Method Get -UseDefaultCredentials 8 | 9 | $releases = @() 10 | 11 | $projectsResult.value | % { 12 | 13 | $project = $_.name 14 | 15 | $url = "$ProjectCollection/$project/_apis/release/releases?api-version=2.2-preview.1" 16 | 17 | $result = Invoke-RestMethod $url -Method Get -UseDefaultCredentials 18 | 19 | if ($result.count -gt 0){ 20 | 21 | $result.value | % { 22 | 23 | $releases += [pscustomobject]@{ 24 | Project = $project 25 | Name = $_.Name 26 | Status = $_.status 27 | CreatedOn = [datetime]$_.createdOn 28 | ModifiedOn = [datetime]$_.modifiedOn 29 | } 30 | } 31 | } 32 | } 33 | 34 | $releases | Sort-Object -Property ModifiedOn -Descending | Format-Table -AutoSize -------------------------------------------------------------------------------- /DevOps/DevOps/Get-VSTSRelease.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param ( 3 | [Parameter(Mandatory=$true, Position=0)] 4 | $Account, 5 | 6 | [Parameter(Mandatory=$true, Position=1)] 7 | $Accesstoken 8 | ) 9 | 10 | $passkey = ":$($Accesstoken)" 11 | $encodedKey = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($passkey)) 12 | $token = "Basic $encodedKey" 13 | 14 | $projectsResult = Invoke-RestMethod "https://$Account.visualstudio.com/_apis/projects?api-version=1.0" -Method Get -Headers @{ Authorization = $token } 15 | 16 | $releases = @() 17 | 18 | $projectsResult.value | % { 19 | 20 | $project = $_.name 21 | 22 | $url = "https://$Account.vsrm.visualstudio.com/$project/_apis/release/releases?api-version=3.0-preview.2" 23 | 24 | $result = Invoke-RestMethod $url -Method Get -Headers @{ Authorization = $token } 25 | 26 | if ($result.count -gt 0){ 27 | 28 | $result.value | % { 29 | 30 | $releases += [pscustomobject]@{ 31 | Project = $project 32 | Name = $_.Name 33 | Status = $_.status 34 | CreatedOn = [datetime]$_.createdOn 35 | ModifiedOn = [datetime]$_.modifiedOn 36 | } 37 | } 38 | } 39 | } 40 | 41 | $releases | Sort-Object -Property ModifiedOn -Descending | Format-Table -AutoSize -------------------------------------------------------------------------------- /DevOps/DevOps/Restore-Website.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Restores a website. 4 | .DESCRIPTION 5 | Restores a website to its original location. 6 | .EXAMPLE 7 | Restore-Website -WebsiteName LSK.Portal 8 | #> 9 | [CmdletBinding()] 10 | Param 11 | ( 12 | [Parameter(Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)] 13 | [string] 14 | $WebSiteName, 15 | 16 | [string] 17 | $WebSitePath = "C:\inetpub\wwwroot\", 18 | 19 | [string] 20 | $BackupPath = "C:\Deployment\Backup\" 21 | ) 22 | 23 | Begin 24 | { 25 | $BackupFolder = Join-Path $BackupPath $WebSiteName 26 | $WebSiteFullName = Join-Path $WebSitePath $WebSiteName 27 | 28 | if (-not (Test-Path $BackupFolder)) 29 | { 30 | $message = "Backup '$BackupFolder' does not exist." 31 | $exception = New-Object InvalidOperationException $message 32 | $errorID = 'FolderNotFound' 33 | $errorCategory = [Management.Automation.ErrorCategory]::InvalidArgument 34 | $target = $BackupFolder 35 | $errorRecord = New-Object Management.Automation.ErrorRecord $exception, $errorID, $errorCategory, $target 36 | $PSCmdlet.ThrowTerminatingError($errorRecord) 37 | } 38 | 39 | if (-not (Test-Path $WebSiteFullName)) 40 | { 41 | Write-Verbose "Create folder '$WebSiteFullName'..." 42 | md $WebSiteFullName | Out-Null 43 | Write-Verbose "Done." 44 | } 45 | } 46 | 47 | Process 48 | { 49 | Write-Verbose "Moving web site '$WebsiteName' from backup location '$BackupFolder' to '$WebSiteFullName'..." 50 | Get-ChildItem -Path $BackupFolder | % { 51 | Write-Verbose "Copy '$_.FullName'..." 52 | Copy-Item $_.FullName "$WebSiteFullName" -Force -Recurse 53 | 54 | Write-Verbose "Done." 55 | } 56 | } 57 | 58 | End 59 | { 60 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ScriptRepository 2 | A repo for storing scripts I might want to reuse... 3 | 4 | ## Description 5 | Look at each folder for its own readme. The scripts should more or less all deal with DSC, TFS, ReleaseManagemnt. 6 | If not they are about SharePoint or O365 and will later be transformed into the xSharePointAdministration or xSharePointProvisioning resources. 7 | -------------------------------------------------------------------------------- /Set-RepositoryPermission/Readme.md: -------------------------------------------------------------------------------- 1 | # NAME 2 | Set-RepositoryPermission.ps1 3 | 4 | # SYNOPSIS 5 | Set the permissions for a git repository to enforce naming policies 6 | 7 | # SYNTAX 8 | ``` PowerShell 9 | .\Set-RepositoryPermission.ps1 [-AccountOrCollection] [-ProjectName] [-Repository] [[-AllowedFolders] ] [[-AdminAllowedFolders] ] [[-PathToTfExe] ] [-WhatIf] [-Confirm] [] 10 | ``` 11 | 12 | # DESCRIPTION 13 | This script sets the permission of a git repository. It denys to create branches in the >>root and only allows the creation of branches in specific folders. 14 | 15 | # PARAMETERS 16 | -AccountOrCollection 17 | Your VSTS account or TFS project collection url 18 | 19 | -ProjectName 20 | The name of the project that contains your repository 21 | 22 | -Repository 23 | The name of the repository 24 | 25 | -AllowedFolders 26 | The list of folders where contributors can create branches in (default: features, bufixes, users) 27 | 28 | -AdminAllowedFolders 29 | The list of folders where contributors can create branches in (default: features, bufixes, users, releases, master) 30 | 31 | -PathToTfExe 32 | The path where tf.exe is installed (default: Visual Studio 2017 location 'C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer'). 33 | 34 | -WhatIf [] 35 | 36 | -Confirm [] 37 | 38 | 39 | This cmdlet supports the common parameters: Verbose, Debug, 40 | ErrorAction, ErrorVariable, WarningAction, WarningVariable, 41 | OutBuffer, PipelineVariable, and OutVariable. For more information, see 42 | about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). 43 | 44 | # Examples 45 | 46 | ## -------------------------- EXAMPLE 1 -------------------------- 47 | ``` PowerShell 48 | C:\PS>Set-RepositoryPermission.ps1 -AccountOrCollection https://tfs.contoso.com/tfs/DefaultCollection -ProjectName MyProject -Repository MyProject -Verbose 49 | ``` 50 | 51 | ## -------------------------- EXAMPLE 2 -------------------------- 52 | ``` PowerShell 53 | C:\PS>Set-RepositoryPermission.ps1 -AccountOrCollection https://constoso.visualstudio.com -ProjectName MyProject -Repository MyProject -Verbose 54 | ``` 55 | 56 | ## -------------------------- EXAMPLE 3 -------------------------- 57 | ``` PowerShell 58 | C:\PS>Set-RepositoryPermission.ps1 -AccountOrCollection https://constoso.visualstudio.com -ProjectName MyProject -Repository MyProject -AllowedFolders @("features", "users") 59 | ``` 60 | 61 | 62 | # REMARKS 63 | To see the examples, type: "get-help .\Set-RepositoryPermission.ps1 -examples". 64 | For more information, type: "get-help .\Set-RepositoryPermission.ps1 -detailed". 65 | For technical information, type: "get-help .\Set-RepositoryPermission.ps1 -full". 66 | -------------------------------------------------------------------------------- /Set-RepositoryPermission/Set-RepositoryPermission.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Set the permissions for a git repository to enforce naming policies 4 | .DESCRIPTION 5 | This script sets the permission of a git repository. It denys to create branches in the root and only allows the creation of branches in specific folders. 6 | .EXAMPLE 7 | Set-RepositoryPermission.ps1 -AccountOrCollection https://tfs.contoso.com/tfs/DefaultCollection -ProjectName MyProject -Repository MyProject -Verbose 8 | .EXAMPLE 9 | Set-RepositoryPermission.ps1 -AccountOrCollection https://constoso.visualstudio.com -ProjectName MyProject -Repository MyProject -Verbose 10 | .EXAMPLE 11 | Set-RepositoryPermission.ps1 -AccountOrCollection https://constoso.visualstudio.com -ProjectName MyProject -Repository MyProject -AllowedFolders @("features", "users") 12 | #> 13 | [CmdletBinding(SupportsShouldProcess=$true)] 14 | Param 15 | ( 16 | # Your VSTS account or TFS project collection url 17 | [Parameter(Mandatory=$true)] 18 | [ValidateNotNullOrEmpty()] 19 | [ValidatePattern("http://.*|https://.*")] 20 | [string] 21 | $AccountOrCollection, 22 | 23 | # The name of the project that contains your repository 24 | [Parameter(Mandatory=$true)] 25 | [ValidateNotNullOrEmpty()] 26 | [string] 27 | $ProjectName, 28 | 29 | # The name of the repository 30 | [Parameter(Mandatory=$true)] 31 | [ValidateNotNullOrEmpty()] 32 | [string] 33 | $Repository, 34 | 35 | # The list of folders where contributors can create branches in (default: features, bufixes, users) 36 | [Parameter(Mandatory=$false)] 37 | [string[]] 38 | $AllowedFolders = @("features", "bugfixes", "users"), 39 | 40 | # The list of folders where contributors can create branches in (default: features, bufixes, users, releases, master) 41 | [Parameter(Mandatory=$false)] 42 | [string[]] 43 | $AdminAllowedFolders = @("features", "bugfixes", "users", "releases", "master"), 44 | 45 | # The path where tf.exe is installed (default: Visual Studio 2017 location 'C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer'). 46 | [Parameter(Mandatory=$false)] 47 | [ValidateNotNullOrEmpty()] 48 | [ValidateScript({ Test-Path -PathType Container $_ })] 49 | [string] 50 | $PathToTfExe = "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer" 51 | ) 52 | 53 | pushd $PathToTfExe 54 | 55 | 56 | # block the Create Branch permission at the repository root for the project's contributors 57 | Write-Verbose "Deny the 'CreateBranch' permission to the group '[$ProjectName]\Contributors' in repository '$Repository' ($AccountOrCollection)." 58 | if ($pscmdlet.ShouldProcess("$Repository", "Deny CreateBranch")) 59 | { 60 | & .\tf.exe git permission /deny:CreateBranch /group:[$ProjectName]\Contributors /collection:$AccountOrCollection /teamproject:$ProjectName /repository:$Repository 61 | } 62 | 63 | # block the Create Branch permission at the repository root for the project's admins 64 | Write-Verbose "Deny the 'CreateBranch' permission to the group '[$ProjectName]\Project Administrators' in repository '$Repository' ($AccountOrCollection)." 65 | if ($pscmdlet.ShouldProcess("$Repository", "Deny CreateBranch")) 66 | { 67 | & .\tf.exe git permission /deny:CreateBranch /group:"[$ProjectName]\Project Administrators" /collection:$AccountOrCollection /teamproject:$ProjectName /repository:$Repository 68 | } 69 | 70 | # Allow contributors to create branches under configured folders 71 | $AllowedFolders | ForEach-Object { 72 | 73 | Write-Verbose "Allow the 'CreateBranch' permission to the group '[$ProjectName]\Contributors' for folder '$_' in repository '$Repository' ($AccountOrCollection)." 74 | if ($pscmdlet.ShouldProcess("$Repository/$_", "Allow CreateBranch")) 75 | { 76 | & .\tf.exe git permission /allow:CreateBranch /group:[$ProjectName]\Contributors /collection:$AccountOrCollection /teamproject:$ProjectName /repository:$Repository /branch:$_ 77 | } 78 | } 79 | 80 | # Allow admins to create branches under configured folders 81 | $AdminAllowedFolders | ForEach-Object { 82 | 83 | Write-Verbose "Allow the 'CreateBranch' permission to the group '[$ProjectName]\Project Administrators' for folder '$_' in repository '$Repository' ($AccountOrCollection)." 84 | if ($pscmdlet.ShouldProcess("$Repository/$_", "Allow CreateBranch")) 85 | { 86 | & .\tf.exe git permission /allow:CreateBranch /group:"[$ProjectName]\Project Administrators" /collection:$AccountOrCollection /teamproject:$ProjectName /repository:$Repository /branch:$_ 87 | } 88 | } 89 | 90 | popd 91 | 92 | 93 | -------------------------------------------------------------------------------- /TFS/ReleaseVariables/Get-ReleasesWithVariable.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Lists all releases with all environments that contain a specific variable. 4 | .DESCRIPTION 5 | Iterates one or more ProjectCollections and lists all environments in all release definitions in all projects. If the environment contains a CertificateThumbprint it will be displayed as well. 6 | .EXAMPLE 7 | Get-ReleasesWithVariable https:///tfs/ MyVariable 8 | .EXAMPLE 9 | Get-ReleasesWithVariable -ProjectCollections @("https://m/tfs/", "https://m/tfs/") -VariableName MyVariable -verbose 10 | .EXAMPLE 11 | Get-ReleasesWithVariable -AccountName VSTSAccountName -AccessToken PAT -VariableName MyVariable 12 | #> 13 | [CmdletBinding(DefaultParameterSetName='TFS')] 14 | Param 15 | ( 16 | # One or more URLs to project collections on your on premise TFS. 17 | [Parameter(Mandatory=$true, Position=0, ParameterSetName='TFS')] 18 | [string[]] 19 | $ProjectCollections, 20 | 21 | # The name of the VSTS account 22 | [Parameter(Mandatory=$true, Position=0, ParameterSetName='VSTS')] 23 | [string] 24 | $AccountName, 25 | 26 | # The private access token (PAT). See https://www.visualstudio.com/en-us/docs/setup-admin/team-services/use-personal-access-tokens-to-authenticate 27 | [Parameter(Mandatory=$true, Position=1, ParameterSetName='VSTS')] 28 | $Accesstoken, 29 | 30 | # The name of the variable to search for. 31 | [Parameter(Mandatory=$true, Position=1, ParameterSetName='TFS')] 32 | [Parameter(Mandatory=$true, Position=2, ParameterSetName='VSTS')] 33 | [ValidateNotNullOrEmpty()] 34 | [string] 35 | $VariableName 36 | ) 37 | 38 | 39 | if ($PSCmdlet.ParameterSetName -eq "TFS") 40 | { 41 | # TFS On Premise 42 | $apiVersion = "2.2-preview.1" 43 | $additionalParameters = @{ UseDefaultCredentials = $null } 44 | } 45 | else 46 | { 47 | # VSTS 48 | $passkey = ":$($Accesstoken)" 49 | $encodedKey = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($passkey)) 50 | $token = "Basic $encodedKey" 51 | $apiVersion = "3.0-preview.2" 52 | 53 | $ProjectCollections = "https://$AccountName.visualstudio.com" 54 | 55 | $additionalParameters = @{ Headers = @{ Authorization = $token } } 56 | } 57 | 58 | $releases = @() 59 | 60 | $ProjectCollections | % { 61 | 62 | $projectCollection = $_ 63 | Write-Verbose "Processing ProjectCollection '$projectCollection'..." 64 | 65 | $projectsResult = Invoke-RestMethod "$projectCollection/_apis/projects?`$top=1000&api-version=1.0" -Method Get @additionalParameters 66 | 67 | if ($PSCmdlet.ParameterSetName -eq "VSTS") 68 | { 69 | $projectCollection = "https://$AccountName.vsrm.visualstudio.com" 70 | } 71 | 72 | $projectsResult.value | % { 73 | 74 | $project = $_.name 75 | 76 | Write-Host "Processing ProjectCollection '$project'..." 77 | 78 | $url = "$ProjectCollection/$project/_apis/release/definitions?`$expand=environments&api-version=$ApiVersion" 79 | 80 | $result = Invoke-RestMethod $url -Method Get @additionalParameters 81 | 82 | if ($result.count -gt 0){ 83 | 84 | $result.value | % { 85 | 86 | $url = "$ProjectCollection/$project/_apis/release/definitions/$($_.id)?api-version=$ApiVersion" 87 | 88 | $definition = Invoke-RestMethod $url -Method Get @additionalParameters 89 | 90 | $name = $_.Name 91 | 92 | $definition.environments | % { 93 | 94 | if ($null -eq $_.variables.$VariableName) 95 | { 96 | Write-Verbose "Environment '$($_.Name)' does not contain a variable '$VariableName'." 97 | } 98 | else 99 | { 100 | $releases += [pscustomobject]@{ 101 | Collection = $ProjectCollection 102 | Project = $project 103 | Name = $name 104 | Env = $_.Name 105 | $VariableName = $_.variables.$VariableName.value 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | $releases | Sort-Object -Property Collection,Project | Format-Table -AutoSize -------------------------------------------------------------------------------- /TFS/ReleaseVariables/Readme.md: -------------------------------------------------------------------------------- 1 | # Get-ReleasesWithVariables 2 | ## Synopsis 3 | Lists all releases with all environments that contain a specific variable. 4 | ## DESCRIPTION 5 | Iterates one or more ProjectCollections and lists all environments in all release definitions in all projects. If the environment contains a CertificateThumbprint it will be displayed as well. 6 | ## EXAMPLE 7 | Get-ReleasesWithVariable https:///tfs/ MyVariable 8 | ## EXAMPLE 9 | Get-ReleasesWithVariable -ProjectCollections @("https://m/tfs/", "https://m/tfs/") -VariableName MyVariable -verbose 10 | ## EXAMPLE 11 | Get-ReleasesWithVariable -AccountName VSTSAccountName -AccessToken PAT -VariableName MyVariable 12 | 13 | # Update-ReleaseVariables 14 | ## Synopsis 15 | Updates a variable with a given value in all release definitions. 16 | ## DESCRIPTION 17 | If you have specific values (like server names or certificate thumbprints) in a lot of release definitions, you may need to update them at once. 18 | The script iterates all projects and checks for a value and replaces it. 19 | 20 | Use -whatif to test without updating the values. 21 | ## EXAMPLE 22 | Update-ReleaseVariables.ps1 -AccountName -Accesstoken -VariableName -OldValues -NewValues -Verbose -WhatIf 23 | ## EXAMPLE 24 | Update-ReleaseVariables.ps1 -AccountName -Accesstoken -VariableName -OldValues @("old1", "old2") -NewValues @("new1", "new2") -Verbose -WhatIf -------------------------------------------------------------------------------- /TFS/ReleaseVariables/Update-ReleaseVariables.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Updates a variable with a given value in all release definitions. 4 | .DESCRIPTION 5 | If you have specific values (like server names or certificate thumbprints) in a lot of release definitions, you may need to update them at once. 6 | The script iterates all projects and checks for a value and replaces it. 7 | 8 | Use -whatif to test without updating the values. 9 | .EXAMPLE 10 | Update-ReleaseVariables.ps1 -AccountName -Accesstoken -VariableName -OldValues -NewValues -Verbose -WhatIf 11 | .EXAMPLE 12 | Update-ReleaseVariables.ps1 -AccountName -Accesstoken -VariableName -OldValues @("old1", "old2") -NewValues @("new1", "new2") -Verbose -WhatIf 13 | #> 14 | [CmdletBinding(SupportsShouldProcess=$true)] 15 | Param 16 | ( 17 | # One or more URLs to project collections on your on premise TFS. 18 | [Parameter(Mandatory=$true, Position=0, ParameterSetName='TFS')] 19 | [string[]] 20 | $ProjectCollections, 21 | 22 | # The name of the VSTS account 23 | [Parameter(Mandatory=$true, Position=0, ParameterSetName='VSTS')] 24 | [string] 25 | $AccountName, 26 | 27 | # The private access token (PAT). See https://www.visualstudio.com/en-us/docs/setup-admin/team-services/use-personal-access-tokens-to-authenticate 28 | [Parameter(Mandatory=$true, Position=1, ParameterSetName='VSTS')] 29 | $Accesstoken, 30 | 31 | # The name of the variable to search for. 32 | [Parameter(Mandatory=$true, Position=1, ParameterSetName='TFS')] 33 | [Parameter(Mandatory=$true, Position=2, ParameterSetName='VSTS')] 34 | [ValidateNotNullOrEmpty()] 35 | [string] 36 | $VariableName, 37 | 38 | # The old values for the variable 39 | [Parameter(Mandatory=$true, Position=2, ParameterSetName='TFS')] 40 | [Parameter(Mandatory=$true, Position=3, ParameterSetName='VSTS')] 41 | [string[]] 42 | $OldValues, 43 | 44 | # The new values for the variable 45 | [Parameter(Mandatory=$true, Position=3, ParameterSetName='TFS')] 46 | [Parameter(Mandatory=$true, Position=4, ParameterSetName='VSTS')] 47 | [string[]] 48 | $NewValues 49 | ) 50 | 51 | if ($OldValues.Count -ne $NewValues.Count){ 52 | throw "You must supply the same number for old and new values." 53 | } 54 | 55 | if ($PSCmdlet.ParameterSetName -eq "TFS") 56 | { 57 | # TFS On Premise 58 | $apiVersion = "2.2-preview.1" 59 | $additionalParameters = @{ UseDefaultCredentials = $null } 60 | } 61 | else 62 | { 63 | # VSTS 64 | $passkey = ":$($Accesstoken)" 65 | $encodedKey = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($passkey)) 66 | $token = "Basic $encodedKey" 67 | $apiVersion = "3.0-preview.2" 68 | 69 | $ProjectCollections = "https://$AccountName.visualstudio.com" 70 | 71 | $additionalParameters = @{ Headers = @{ Authorization = $token } } 72 | } 73 | 74 | $releases = @() 75 | 76 | function Update-EnvironmentVariable 77 | { 78 | [CmdletBinding(SupportsShouldProcess=$true)] 79 | Param 80 | ( 81 | # The url to the project (i.e. https://tfs.hugoboss.com/tfs/HBCollaboration/VisitorManagement) 82 | [Parameter(Mandatory=$true, Position=0)] 83 | [string] 84 | $ProjectUrl, 85 | 86 | # The id of the build definition (i.e. 1) 87 | [Parameter(Mandatory=$true, Position=1)] 88 | [int] 89 | $DefinitionId, 90 | 91 | # The id of the environment (i.e. 2) 92 | [Parameter(Mandatory=$true, Position=2)] 93 | [int] 94 | $EnvironmentId, 95 | 96 | # The name of the variable to update (i.e. CertificateThumbprint) 97 | [Parameter(Mandatory=$true, Position=3)] 98 | [string] 99 | $VariableName, 100 | 101 | # The new value for the variable 102 | [Parameter(Mandatory=$true, Position=4)] 103 | [string] 104 | $VariableValue, 105 | 106 | $additionalParameters 107 | ) 108 | 109 | $url = "$ProjectUrl/_apis/release/definitions/$($DefinitionId)?api-version=$ApiVersion" 110 | 111 | $definition = Invoke-RestMethod $url -Method Get @additionalParameters 112 | 113 | $env = $definition.environments | ? { $_.id -eq $EnvironmentId } 114 | 115 | $currentValue = $env.variables.$VariableName.value 116 | 117 | $env.variables.$VariableName.value = $VariableValue 118 | 119 | Write-Verbose "Change value of '$VariableName' in environment '$($env.name)' from '$currentValue' to '$VariableValue'." 120 | 121 | $url = "$ProjectUrl/_apis/release/definitions/$($DefinitionId)?api-version=$ApiVersion" 122 | 123 | $body = ConvertTo-Json -InputObject $definition -Depth 100 -Compress 124 | 125 | if ($pscmdlet.ShouldProcess("$url", "Update")) 126 | { 127 | $result = Invoke-RestMethod $url -Method Put -Body $body -ContentType "application/json" @additionalParameters 128 | Write-Verbose "Updated '$url' to revision '$($result.revision)" 129 | } 130 | } 131 | 132 | 133 | $ProjectCollections | % { 134 | 135 | $projectCollection = $_ 136 | Write-Verbose "Processing ProjectCollection '$projectCollection'..." 137 | 138 | $projectsResult = Invoke-RestMethod "$projectCollection/_apis/projects?`$top=1000api-version=1.0" -Method Get @additionalParameters 139 | 140 | if ($PSCmdlet.ParameterSetName -eq "VSTS") 141 | { 142 | $projectCollection = "https://$AccountName.vsrm.visualstudio.com" 143 | } 144 | 145 | $projectsResult.value | % { 146 | 147 | $project = $_.name 148 | 149 | Write-Verbose "Processing project '$project'..." 150 | 151 | $url = "$ProjectCollection/$project/_apis/release/definitions?`$expand=environments&api-version=$ApiVersion" 152 | 153 | $result = Invoke-RestMethod $url -Method Get @additionalParameters 154 | 155 | if ($result.count -gt 0){ 156 | 157 | $result.value | % { 158 | 159 | $url = "$ProjectCollection/$project/_apis/release/definitions/$($_.id)?api-version=$ApiVersion" 160 | 161 | $definition = Invoke-RestMethod $url -Method Get @additionalParameters 162 | 163 | $definitionName = $_.Name 164 | $definitionId = $_.Id 165 | 166 | $definition.environments | % { 167 | 168 | $environmentId = $_.id 169 | $environmentName = $_.Name 170 | 171 | Write-Verbose "Process environment '$environmentName' ($environmentId) in definition '$definitionName' ($definitionId)..." 172 | 173 | $oldValue = $_.variables.$VariableName.value 174 | 175 | if ($OldValues.Contains($oldValue)){ 176 | 177 | $newValue = $NewValues[$OldValues.IndexOf($oldValue)] 178 | 179 | Write-Verbose "Found old value '$oldValue' in environemnt. Replace it with '$newValue'" 180 | 181 | Update-EnvironmentVariable -ProjectUrl "$ProjectCollection/$project" -DefinitionId $definitionId -EnvironmentId $environmentId -VariableName $VariableName -VariableValue $newValue -additionalParameters $additionalParameters 182 | 183 | $releases += [pscustomobject]@{ 184 | Collection = $ProjectCollection 185 | Project = $project 186 | Name = $definitionName 187 | Env = $_.Name 188 | Match = $oldValue 189 | Replacement = $newValue 190 | } 191 | 192 | } 193 | else 194 | { 195 | Write-Verbose "No matching value found in environment." 196 | } 197 | } 198 | } 199 | } 200 | } 201 | } 202 | 203 | $releases | Sort-Object -Property Collection,Project -Descending | Format-Table -AutoSize -------------------------------------------------------------------------------- /TFSBuild/README.md: -------------------------------------------------------------------------------- 1 | # TFS Build Scripts 2 | A solution for build scripts that can be used in vNext build tasks. 3 | 4 | ## AssemblyVersion 5 | A build script that can be included in TFS 2015 or Visual Studio Online (VSO) vNevt builds that update the version of all assemblies in a workspace. 6 | It uses the name of the build to extract the version number and updates all AssemblyInfo.cs files to use the new version. 7 | 8 | Add the variables MajorVersion and MinorVersion to your build. Set the Build number format (Tab General) to: 9 | 10 | $(BuildDefinitionName)_$(MajorVersion).$(MinorVersion)_$(Year:yy)$(DayOfYear)$(rev:.rr) 11 | 12 | -------------------------------------------------------------------------------- /TFSBuild/TFSBuild.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F5034706-568F-408A-B7B3-4D38C6DB8A32}") = "AssemblyVersion", "TFSBuild\AssemblyVersion\AssemblyVersion.pssproj", "{6CAFC0C6-A428-4D30-A9F9-700E829FEA51}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D8006F12-363A-4070-B528-80A80A07CD60}" 9 | ProjectSection(SolutionItems) = preProject 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | EndGlobal 28 | -------------------------------------------------------------------------------- /TFSBuild/TFSBuild/AssemblyVersion/AssemblyVersion.pssproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | 2.0 6 | 6CAFC0C6-A428-4d30-A9F9-700E829FEA51 7 | Exe 8 | MyApplication 9 | MyApplication 10 | AssemblyVersion 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug\ 17 | DEBUG;TRACE 18 | prompt 19 | 4 20 | 21 | 22 | pdbonly 23 | true 24 | bin\Release\ 25 | TRACE 26 | prompt 27 | 4 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /TFSBuild/TFSBuild/AssemblyVersion/README.md: -------------------------------------------------------------------------------- 1 | # Set assembly and app versions in TFS team build 2 | ## Introduction 3 | One of the most common customizations in TFS XAML build templates was to automatically update the 4 | assembly version number. This can also be done in build vNext using a small power shell script. 5 | 6 | I also added the option to apply the version to SharePoint / Office 365 apps. 7 | 8 | ## Description 9 | Please see [my post on writeabout.net]("http://writeabout.net/2015/11/01/set-assembly-and-app-version-to-a-matching-build-name-in-tfs-2015-or-vso-build-vnext/") for more 10 | details how to use the script in you build. 11 | 12 | -------------------------------------------------------------------------------- /TFSBuild/TFSBuild/AssemblyVersion/Set-AssemblyVersion/Set-AssemblyVersion.Tests.ps1: -------------------------------------------------------------------------------- 1 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".") 3 | . "$here\$sut" 4 | 5 | 6 | Describe "Set-AssemblyVersion" { 7 | 8 | context "Given the SourceDirectory does not exit, it"{ 9 | 10 | It "throws an exception"{ 11 | { Set-AssemblyVersion -SourceDirectory "$TestDrive\XYZ" -BuildNumber "Dev_1.0.20150922.01" } | Should throw 12 | } 13 | } 14 | 15 | context "Given the SourceDirectory does exit, it"{ 16 | setup -File "SourceDir\Properties\AssemblyInfo.cs" -Content "[assembly: AssemblyVersion(""1.0.0.0"")]" 17 | 18 | It "does not throw an exception"{ 19 | { Set-AssemblyVersion -SourceDirectory "$TestDrive\SourceDir" -BuildNumber "Dev_1.0.20150922.01" } | Should not Throw 20 | } 21 | 22 | $content = Get-Content "$TestDrive\SourceDir\Properties\AssemblyInfo.cs" 23 | 24 | It "sets the version to the extracted verion of the build number"{ 25 | $content | should match "[assembly: AssemblyVersion(""1.0.20150922.01"")]" 26 | } 27 | } 28 | 29 | context "Given the SourceDirectory does exit, it"{ 30 | setup -File "SourceDir\Properties\AssemblyInfo.cs" -Content "[assembly: AssemblyVersion(""1.0.0.0"")]" 31 | Mock Get-AppManifest { return Get-Item "$TestDrive\SourceDir\Properties\AssemblyInfo.cs" } -Verifiable 32 | Mock Set-AppManifest -Verifiable 33 | 34 | Set-AssemblyVersion -SourceDirectory "$TestDrive\SourceDir" -BuildNumber "Dev_1.0.20150922.01" -SetAppVersion 35 | 36 | It "sets the version for apps, if switch is present"{ 37 | 38 | Assert-MockCalled Get-AppManifest -Times 1 39 | Assert-MockCalled Set-AppManifest -Times 1 40 | } 41 | } 42 | } 43 | 44 | Describe "Get-Version"{ 45 | 46 | context "Given a valid regex, it"{ 47 | 48 | It "returns the expected version if one match was found"{ 49 | $actual = Get-Version -BuildNumber "Build HelloWorld_0000.00.00.0" -VersionFormat "\d+\.\d+\.\d+\.\d+" 50 | 51 | $actual | Should be "0000.00.00.0" 52 | } 53 | 54 | It "returns the first result, if two results were found"{ 55 | $actual = Get-Version -BuildNumber "1111.00.00.0_Build HelloWorld_0000.00.00.0" -VersionFormat "\d+\.\d+\.\d+\.\d+" 56 | 57 | $actual | Should be "1111.00.00.0" 58 | } 59 | 60 | It "throws an exception, if no match could be found"{ 61 | 62 | { Get-Version -BuildNumber "Build HelloWorld_0" -VersionFormat "\d+\.\d+\.\d+\.\d+" } | Should throw 63 | } 64 | } 65 | 66 | context "Given an invalid regex, it"{ 67 | 68 | It "throws an exception."{ 69 | 70 | { Get-Version -BuildNumber "Build HelloWorld_0" -VersionFormat "lala" } | Should throw 71 | } 72 | } 73 | } 74 | 75 | Describe "Get-Files"{ 76 | 77 | context "Given two projects in a solution folder, it"{ 78 | 79 | setup -File "SourceDir\SolutionDir\Project1\Properties\AssemblyInfo.cs" 80 | setup -File "SourceDir\SolutionDir\Project2\Properties\AssemblyInfo.cs" 81 | 82 | $actual = Get-Files -SourceDir $TestDrive\SourceDir 83 | 84 | It "returns two Assembly.cs files"{ 85 | $actual | should not BeNullOrEmpty 86 | $actual.Count | should be 2 87 | } 88 | } 89 | 90 | context "Given two projects in different subfolders, it"{ 91 | 92 | setup -File "SourceDir\SolutionDir\Subfolder1\Project1\Properties\AssemblyInfo.cs" 93 | setup -File "SourceDir\SolutionDir\Sub1\Sub2\Project2\Properties\AssemblyInfo.cs" 94 | 95 | $actual = Get-Files -SourceDir $TestDrive\SourceDir 96 | 97 | It "returns two Assembly.cs files"{ 98 | $actual | should not BeNullOrEmpty 99 | $actual.Count | should be 2 100 | } 101 | } 102 | 103 | context "Given a visual basic project, it"{ 104 | 105 | setup -File "SourceDir\Properties\AssemblyInfo.vb" 106 | 107 | $actual = Get-Files -SourceDir $TestDrive\SourceDir 108 | 109 | It "returns two Assembly.cs files"{ 110 | $actual | should not BeNullOrEmpty 111 | $actual.Count | should be 1 112 | } 113 | } 114 | } 115 | 116 | Describe "Set-FileContent"{ 117 | 118 | context "Given an AssemblyInfo.cs file, it"{ 119 | 120 | setup -File "SourceDir\AssemblyInfo.cs" -Content "//comment`r`n[assembly: AssemblyVersion(""1.0.0.0"")]`r`n[assembly: AssemblyFileVersion(""1.0.0.0"")]`r`n" 121 | 122 | $file = Get-Item "$TestDrive\SourceDir\AssemblyInfo.cs" 123 | 124 | Set-FileContent $file "15.88.99.17" "\d+\.\d+\.\d+\.\d+" 125 | 126 | $content = Get-Content "$TestDrive\SourceDir\AssemblyInfo.cs" 127 | 128 | It "sets the Version to the given version"{ 129 | $content[1] | should match "[assembly: AssemblyVersion(""15.88.99.1"")]" 130 | } 131 | 132 | It "sets the FileVersion to the given version"{ 133 | $content[2] | should match "[assembly: AssemblyFileVersion(""15.88.99.1"")]" 134 | } 135 | } 136 | } 137 | 138 | Describe "Get-AppManifest"{ 139 | 140 | context "Given two apps in different subfolders, it"{ 141 | 142 | setup -File "SourceDir\SolutionDir\Subfolder1\Project1\AppManifest.xml" 143 | setup -File "SourceDir\SolutionDir\Sub1\Sub2\Project2\AppManifest.xml" 144 | 145 | $actual = Get-AppManifest -SourceDir $TestDrive\SourceDir 146 | 147 | It "returns two app manifest files"{ 148 | $actual | should not BeNullOrEmpty 149 | $actual.Count | should be 2 150 | } 151 | } 152 | } 153 | 154 | Describe "Set-AppManifest"{ 155 | 156 | context "Given an app manifest file, it"{ 157 | 158 | $content = " 159 | 165 | 166 | CalculatorApp 167 | ~remoteAppUrl/?{StandardTokens} 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | " 177 | 178 | setup -File "SourceDir\AppManifest.xml" -Content $content 179 | 180 | $file = Get-Item "$TestDrive\SourceDir\AppManifest.xml" 181 | 182 | Set-AppManifest $file "9.9.9.9" 183 | 184 | [xml]$result = Get-Content $file 185 | 186 | It "sets the version attribute to the desired value."{ 187 | $result.App.Version | should be "9.9.9.9" 188 | } 189 | } 190 | } 191 | 192 | -------------------------------------------------------------------------------- /TFSBuild/TFSBuild/AssemblyVersion/Set-AssemblyVersion/Set-AssemblyVersion.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Sets the assembly version of all assemblies in the source directory. 4 | .DESCRIPTION 5 | A build script that can be included in TFS 2015 or Visual Studio Online (VSO) vNevt builds that update the version of all assemblies in a workspace. 6 | It uses the name of the build to extract the version number and updates all AssemblyInfo.cs files to use the new version. 7 | .EXAMPLE 8 | Set-AssemblyVersion 9 | .EXAMPLE 10 | Set-AssemblyVersion -SourceDirectory $Env:BUILD_SOURCESDIRECTORY -BuildNumber $Env:BUILD_BUILDNUMBER 11 | .EXAMPLE 12 | Set-AssemblyVersion -SourceDirectory ".\SourceDir" -BuildNumber "Dev_1.0.20150922.01" -VersionFormat "\d+\.\d+\.\d+\.\d+" 13 | #> 14 | 15 | [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] 16 | [Alias()] 17 | [OutputType([int])] 18 | Param 19 | ( 20 | # The path to the source directory. Default $Env:BUILD_SOURCESDIRECTORY is set by TFS. 21 | [Parameter(Mandatory=$false, Position=0)] 22 | [ValidateNotNullOrEmpty()] 23 | [string]$SourceDirectory = $Env:BUILD_SOURCESDIRECTORY, 24 | 25 | # The build number. Default $Env:BUILD_BUILDNUMBER is set by TFS and must be configured according your regex. 26 | [Parameter(Mandatory=$false, Position=1)] 27 | [ValidateNotNullOrEmpty()] 28 | [string]$BuildNumber = $Env:BUILD_BUILDNUMBER, 29 | 30 | # The build number. Default $Env:BUILD_BUILDNUMBER is set by TFS and must be configured according your regex. 31 | [Parameter(Mandatory=$false, Position=2)] 32 | [ValidateNotNullOrEmpty()] 33 | [string]$VersionFormat = "\d+\.\d+\.\d+\.\d+", 34 | 35 | # Set the version number also in all AppManifest.xml files. 36 | [Parameter(Mandatory=$false)] 37 | [switch]$SetAppVersion 38 | ) 39 | 40 | <# 41 | .Synopsis 42 | Sets the assembly version of all assemblies in the source directory. 43 | .DESCRIPTION 44 | A build script that can be included in TFS 2015 or Visual Studio Online (VSO) vNevt builds that update the version of all assemblies in a workspace. 45 | It uses the name of the build to extract the version number and updates all AssemblyInfo.cs files to use the new version. 46 | .EXAMPLE 47 | Set-AssemblyVersion 48 | .EXAMPLE 49 | Set-AssemblyVersion -SourceDirectory $Env:BUILD_SOURCESDIRECTORY -BuildNumber $Env:BUILD_BUILDNUMBER 50 | .EXAMPLE 51 | Set-AssemblyVersion -SourceDirectory ".\SourceDir" -BuildNumber "Dev_1.0.20150922.01" -VersionFormat "\d+\.\d+\.\d+\.\d+" 52 | #> 53 | function Set-AssemblyVersion 54 | { 55 | [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] 56 | [Alias()] 57 | [OutputType([int])] 58 | Param 59 | ( 60 | # The path to the source directory. Default $Env:BUILD_SOURCESDIRECTORY is set by TFS. 61 | [Parameter(Mandatory=$false, Position=0)] 62 | [ValidateNotNullOrEmpty()] 63 | [string]$SourceDirectory = $Env:BUILD_SOURCESDIRECTORY, 64 | 65 | # The build number. Default $Env:BUILD_BUILDNUMBER is set by TFS and must be configured according your regex. 66 | [Parameter(Mandatory=$false, Position=1)] 67 | [ValidateNotNullOrEmpty()] 68 | [string]$BuildNumber = $Env:BUILD_BUILDNUMBER, 69 | 70 | # The build number. Default $Env:BUILD_BUILDNUMBER is set by TFS and must be configured according your regex. 71 | [Parameter(Mandatory=$false, Position=2)] 72 | [ValidateNotNullOrEmpty()] 73 | [string]$VersionFormat = "\d+\.\d+\.\d+\.\d+", 74 | 75 | # Set the version number also in all AppManifest.xml files. 76 | [Parameter(Mandatory=$false)] 77 | [switch]$SetAppVersion 78 | ) 79 | 80 | if (-not (Test-Path $SourceDirectory)) { 81 | throw "The directory '$SourceDirectory' does not exist." 82 | } 83 | 84 | $Version = Get-Version -BuildNumber $BuildNumber -VersionFormat $VersionFormat 85 | 86 | $files = Get-Files -SourceDirectory $SourceDirectory 87 | 88 | Set-FileContent -Files $files -Version $Version -VersionFormat $VersionFormat 89 | 90 | if ($SetAppVersion.IsPresent) 91 | { 92 | $files = Get-AppManifest -SourceDirectory $SourceDirectory 93 | Set-AppManifest -Files $files -Version $Version 94 | } 95 | } 96 | 97 | function Get-Version 98 | { 99 | [CmdletBinding()] 100 | [Alias()] 101 | [OutputType([string])] 102 | Param 103 | ( 104 | [Parameter(Mandatory=$true, Position=0)] 105 | [ValidateNotNullOrEmpty()] 106 | [string]$BuildNumber, 107 | 108 | [Parameter(Mandatory=$true, Position=1)] 109 | [ValidateNotNullOrEmpty()] 110 | [string]$VersionFormat 111 | ) 112 | 113 | $VersionData = [regex]::matches($BuildNumber,$VersionFormat) 114 | 115 | if ($VersionData.Count -eq 0){ 116 | throw "Could not find version number with format '$VersionFormat' in BUILD_BUILDNUMBER '$BuildNumber'." 117 | } 118 | 119 | return $VersionData[0] 120 | } 121 | 122 | function Get-Files 123 | { 124 | [CmdletBinding()] 125 | [Alias()] 126 | [OutputType([System.IO.FileSystemInfo[]])] 127 | Param 128 | ( 129 | [Parameter(Mandatory=$true, Position=0)] 130 | [ValidateNotNullOrEmpty()] 131 | [string]$SourceDirectory 132 | ) 133 | 134 | $folders = Get-ChildItem $SourceDirectory -Recurse -Include "*Properties*" | Where-Object { $_.PSIsContainer } 135 | 136 | return $folders | ForEach-Object { Get-ChildItem -Path $_.FullName -Recurse -include AssemblyInfo.* } 137 | } 138 | 139 | function Get-AppManifest 140 | { 141 | [CmdletBinding()] 142 | [Alias()] 143 | [OutputType([System.IO.FileSystemInfo[]])] 144 | Param 145 | ( 146 | [Parameter(Mandatory=$true, Position=0)] 147 | [ValidateNotNullOrEmpty()] 148 | [string]$SourceDirectory 149 | ) 150 | 151 | return Get-ChildItem -Path $SourceDirectory -Recurse -Filter "AppManifest.xml" 152 | } 153 | 154 | function Set-FileContent 155 | { 156 | [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] 157 | [OutputType([int])] 158 | Param 159 | ( 160 | [Parameter(Mandatory=$true, Position=0)] 161 | [System.IO.FileSystemInfo[]]$Files, 162 | 163 | [Parameter(Mandatory=$true, Position=1)] 164 | [string]$Version, 165 | 166 | [Parameter(Mandatory=$true, Position=2)] 167 | [string]$VersionFormat 168 | ) 169 | 170 | foreach ($file in $Files) 171 | { 172 | $filecontent = Get-Content $file 173 | 174 | if ($PSCmdlet.ShouldProcess("$file", "Set-AssemblyVersion")) 175 | { 176 | attrib $file -r 177 | $filecontent -replace $VersionFormat, $Version | Out-File $file 178 | Write-Verbose -Message "Applied Version '$Version' $($file.FullName) - version applied" 179 | } 180 | } 181 | } 182 | 183 | function Set-AppManifest 184 | { 185 | [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] 186 | [OutputType([int])] 187 | Param 188 | ( 189 | [Parameter(Mandatory=$true, Position=0)] 190 | [System.IO.FileSystemInfo[]]$Files, 191 | 192 | [Parameter(Mandatory=$true, Position=1)] 193 | [string]$Version 194 | ) 195 | 196 | foreach ($file in $Files) 197 | { 198 | [xml]$xml = Get-Content $file 199 | 200 | $xml.App.Version = $Version 201 | 202 | if ($PSCmdlet.ShouldProcess("$file", "Set-AppManifest")){ 203 | $xml.Save($file.FullName) 204 | } 205 | } 206 | } 207 | 208 | 209 | if (-not ($myinvocation.line.Contains("`$here\`$sut"))) { 210 | Set-AssemblyVersion -SourceDirectory $SourceDirectory -BuildNumber $BuildNumber -VersionFormat $VersionFormat 211 | } -------------------------------------------------------------------------------- /TFSBuild/TFSBuild/Start-Release/Start-Release.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Triggers a release from a build for Visual Studio Release Management 2015 on premises 4 | .DESCRIPTION 5 | Use this script in your builds to trigger a release in Visual Studio Release Management 2015 on premises. 6 | .EXAMPLE 7 | .\Start-Release.ps1 -ReleaseDefinition MyReleaseDefinition -ReleasePath MyPath -TargetStage Dev 8 | .EXAMPLE 9 | .\Start-Release.ps1 -ReleaseDefinition MyReleaseDefinition -ReleasePath MyPath 10 | #> 11 | [CmdletBinding()] 12 | [OutputType([int])] 13 | Param ( 14 | # The name of the release definition 15 | [Parameter(Mandatory=$true, Position=0)] 16 | [ValidateNotNullOrEmpty()] 17 | [string]$ReleaseDefinition, 18 | 19 | # The name of the release path 20 | [Parameter(Mandatory=$true, Position=1)] 21 | [ValidateNotNullOrEmpty()] 22 | [string]$ReleasePathName, 23 | 24 | # The build number. This is automatically set by the build. 25 | [Parameter(Mandatory=$false, Position=2)] 26 | [ValidateNotNullOrEmpty()] 27 | [string]$BuildNumber = $env:TF_BUILD_BUILDNUMBER, 28 | 29 | # The target stage. Adjust the validation set acording to your values 30 | # from the Administration / Manage Picklist / Stage Type in RM Client... 31 | [Parameter(Mandatory=$false, Position=2)] 32 | [ValidateSet("Dev", "Test", "QA", "Prod", "Last")] 33 | [string]$TargetStage = "Last", 34 | 35 | # The api version of the rest api. This might change with later updates. 36 | # Use fiddler to check the version that is used by rm client. 37 | [string]$ApiVersion = "6.0" 38 | ) 39 | 40 | # Set the name of your release management server and the port (default 1000) 41 | $serverName = ":1000" 42 | $releaseManagementService = "http://$serverName/account/releaseManagementService/_apis/releaseManagement" 43 | 44 | # Get the ReleasePathId 45 | $releasePaths = Invoke-RestMethod "$releaseManagementService/ReleaseDefinitionService/ListReleaseDefinitions?api-version=$ApiVersion" -UseDefaultCredentials -Method Post 46 | $releasePathId = $releasePaths.ApplicationVersionList.ApplicationVersion | ? { $_.Name -eq $ReleasePathName } | % { $_.ReleasePathId } 47 | 48 | # Get the TargetStageId 49 | $releasePath = Invoke-RestMethod "$releaseManagementService/ConfigurationService/GetReleasePath?id=$releasePathId&api-version=$ApiVersion" -UseDefaultCredentials -Method Post 50 | $stages = $releasePath.ReleasePath.Stages.Stage 51 | 52 | if ($TargetStage -ne "Last") { 53 | $targetStageId = $stages | ? { $_.StageTypeName -eq $TargetStage } | % { $_.Id } 54 | }else { 55 | $targetStageId = $stages | Select-Object -Last 1 | % { $_.Id } 56 | } 57 | 58 | # Create the release name 59 | $releaseName = "Release: $(Get-Date -Format G)" 60 | 61 | # Create the property bag 62 | $deploymentPropertyBag = "{""ReleaseName"" : ""$releaseName"",""ReleaseBuild"": ""$BuildNumber"",""ReleaseBuildChangeset"" : """",""TargetStageId"": ""$targetStageId"",""ProviderHostedApp:Build"" : """",""ProviderHostedApp:BuildChangesetRange"" : ""-1,-1""}" 63 | $propertyBag = [System.Uri]::EscapeDataString($deploymentPropertyBag) 64 | 65 | $uri = "$releaseManagementService/OrchestratorService/InitiateRelease?releaseTemplateName=$releaseDefinition&deploymentPropertyBag=$propertyBag&api-version=6.0" 66 | 67 | $result = Invoke-RestMethod -Uri $uri -Method Post -UseDefaultCredentials 68 | 69 | if ($result.ErrorMessage) { 70 | throw $result.ErrorMessage 71 | } 72 | 73 | return $result --------------------------------------------------------------------------------