├── AWS_ASG_EnterStandby.ps1 ├── AWS_ASG_ExitStandby.ps1 ├── AzureDevOps_Get-ReleasesToPromote.ps1 ├── Copy-AzDevOpsBuildDefinition.ps1 ├── Delete-AzureRepos.ps1 ├── GAC_RegisterAssemblies.ps1 ├── GenerateAzureSASKey.ps1 ├── Get-InstalledSoftware.ps1 ├── Get-IssuesWorkCompleted.ps1 ├── Get-UpdatedAppForAzDevOpsBuild.ps1 ├── IIS_Deploy.psm1 ├── IIS_SetPropertiesForFasterStartup.ps1 ├── JBoss_FTPUploadLogs.ps1 ├── O365_UndeliverableMessageExport.ps1 ├── Octopus_UpdateVariable.ps1 ├── README.md ├── SSIS_DeployISPAC.ps1 ├── TFS_AggregateCodeCoverageResults.ps1 ├── TFS_CreateBranchPolicies.ps1 ├── TFS_GetCommitAssociatedToBuild.ps1 ├── TFS_PauseBuildsPerEnvironment.ps1 ├── TFS_UpdateWorkItemsWithBuildLink.ps1 ├── Update-AzDevOpsBuildAgentPools-.ps1 ├── Update-AzDevOpsReleaseAgentPools.ps1 ├── Update-ReleaseDefinitionsWindowsMachineFileCopyTask.ps1 └── VSTS_CreateReleasePullRequest.ps1 /AWS_ASG_EnterStandby.ps1: -------------------------------------------------------------------------------- 1 | # Part 1 of 2 2 | # Part 1 Places EC2 instance into autoscaling group's standby mode. 3 | # Part 2 Exits standby mode and waits for instance to be InService. 4 | param ( 5 | [Parameter(Mandatory=$true)][string]$ASGNameVariable # Passed in deploy step, example: WebASGName. 6 | ) 7 | # Get EC2 Instance 8 | Try 9 | { 10 | $response = Invoke-RestMethod -Uri "http://169.254.169.254/latest/meta-data/instance-id" -Method Get 11 | If ($response) 12 | { 13 | $instanceId = $response 14 | } 15 | Else 16 | { 17 | Write-Error -Message "Returned Instance ID does not appear to be valid" 18 | Exit 1 19 | } 20 | } 21 | Catch 22 | { 23 | Write-Error -Message "Failed to load instance ID from AWS." -Exception $_.Exception 24 | Exit 1 25 | } 26 | 27 | # Get Stack name and status 28 | # If Stack is being Updated, instances are updated via AutoScaling group policy, 29 | # and there is no need to place instances into StandBy 30 | Try 31 | { 32 | $stackName = Get-EC2Tag -Filter @{ Name="key";Values="aws:cloudformation:stack-name"},@{ Name="resource-id";Values=$instanceID} 33 | $stackInfo = Get-CFNStack -StackName $stackName.Value 34 | 35 | if($stackInfo.StackStatus -eq "UPDATE_IN_PROGRESS"){ 36 | Write-Host "CloudFormation stack updating, this Octopus step will now be skipped." 37 | Exit 38 | } 39 | } 40 | Catch 41 | { 42 | Write-Error -Message "Failed to retrieve CloudFormation stack status from AWS." -Exception $_.Exception 43 | Exit 1 44 | } 45 | 46 | # Get ASG Name using $instanceId and set Octopus output variable to be used in subsequent deploy step AWS_ASG_ExitStandby.ps1 47 | Try 48 | { 49 | $ASGInfo = Get-ASAutoScalingInstance -InstanceId $instanceId 50 | $ASGName = $ASGInfo.AutoScalingGroupName 51 | 52 | If ($ASGName) 53 | { 54 | # Set ASGNameVariable Octopus output variable passed as argument in deploy step (1 ASGNameVariable per server type) 55 | # Referenced in subsequent deploy step AWS_ASG_ExitStandby.ps1: $ASGNameVariable = $OctopusParameters["Octopus.Action[AWS_ASG_EnterStandby.ps1].Output.$ASGNameVariable"] 56 | Write-Host "Setting Octopus output variable $ASGNameVariable to value: $ASGName" 57 | Set-OctopusVariable -name "$ASGNameVariable" -value "$ASGName" 58 | Write-Host "Output variable set." 59 | } 60 | Else 61 | { 62 | Write-Error -Message "Returned Auto Scaling Group name does not appear to be valid" 63 | Exit 1 64 | } 65 | } 66 | Catch 67 | { 68 | Write-Error -Message "Failed to retrieve Auto Scaling Group name from AWS." -Exception $_.Exception 69 | Exit 1 70 | } 71 | 72 | # Place instance in standby mode if InService, skip if already in standby mode. 73 | Try 74 | { 75 | $instanceState = (Get-ASAutoScalingInstance -InstanceId $instanceId).LifecycleState 76 | 77 | If($instanceState -eq "InService") 78 | { 79 | Write-Host "Placing instance: $instanceId into standby mode for ASG: $ASGName" 80 | Enter-ASStandby -InstanceId $instanceId -AutoScalingGroupName $ASGName -ShouldDecrementDesiredCapacity $true -Force 81 | Write-Host "Instance $instanceId is now in standby mode" 82 | } 83 | ElseIf($instanceState -eq "Standby") 84 | { 85 | Write-Host "Instance already in standby" 86 | } 87 | Else 88 | { 89 | Write-Error -Message "Error: Instance is not InService or Standby mode." -Exception $_.Exception 90 | Exit 1 91 | } 92 | } 93 | Catch 94 | { 95 | Write-Error -Message "Failed to place instance in standby mode." -Exception $_.Exception 96 | Exit 1 97 | } 98 | -------------------------------------------------------------------------------- /AWS_ASG_ExitStandby.ps1: -------------------------------------------------------------------------------- 1 | # Part 2 of 2 2 | # Part 1 Places EC2 instance into autoscaling group's standby mode. 3 | # Part 2 Exits standby mode and waits for instance to be InService. 4 | 5 | param ( 6 | [Parameter(Mandatory=$true)][string]$ASGEnterStandbyDeployStep, # Deploy step name of AWS_ASG_EnterStandby.ps1 7 | [Parameter(Mandatory=$true)][string]$ASGNameVariable, # Variable name that is set by AWS_ASG_EnterStandby.ps1 for ASG Name 8 | [Parameter(Mandatory=$true)][string]$registrationCheckInterval, 9 | [Parameter(Mandatory=$true)][string]$maxRegistrationCheckCount 10 | ) 11 | 12 | # Get EC2 Instance 13 | Try 14 | { 15 | $response = Invoke-RestMethod -Uri "http://169.254.169.254/latest/meta-data/instance-id" -Method Get 16 | If ($response) 17 | { 18 | $instanceId = $response 19 | Write-Host "Instance ID: $instanceId" 20 | } 21 | Else 22 | { 23 | Write-Error -Message "Returned Instance ID does not appear to be valid" 24 | Exit 1 25 | } 26 | } 27 | Catch 28 | { 29 | Write-Error -Message "Failed to load instance ID from AWS." -Exception $_.Exception 30 | Exit 1 31 | } 32 | 33 | # Get Stack name and status 34 | # If Stack is being Updated, instances are updated via AutoScaling group policy, 35 | # and there is no need to place instances into StandBy 36 | Try 37 | { 38 | 39 | $stackName = Get-EC2Tag -Filter @{ Name="key";Values="aws:cloudformation:stack-name"},@{ Name="resource-id";Values=$instanceID} 40 | $stackInfo = Get-CFNStack -StackName $stackName.Value 41 | 42 | if($stackInfo.StackStatus -eq "UPDATE_IN_PROGRESS"){ 43 | Write-Host "CloudFormation stack updating, this Octopus step will now be skipped." 44 | Exit 45 | } 46 | } 47 | Catch 48 | { 49 | Write-Error -Message "Failed to retrieve CloudFormation stack status from AWS." -Exception $_.Exception 50 | Exit 1 51 | } 52 | 53 | # Get ASG Name variable from previous deploy step (AWS_ASG_EnterStandby.ps1) 54 | Try 55 | { 56 | $ASGName = $OctopusParameters["Octopus.Action[$ASGEnterStandbyDeployStep].Output.$ASGNameVariable"] 57 | Write-Host "Auto Scaling Group Name: $ASGName" 58 | If (!$ASGName) 59 | { 60 | Write-Error -Message "Returned Auto Scaling Group Name does not appear to be valid" 61 | Exit 1 62 | } 63 | } 64 | Catch 65 | { 66 | Write-Error -Message "Failed to get ASGNameVariable output variable from Octopus" -Exception $_.Exception 67 | Exit 1 68 | } 69 | 70 | # Exit standby mode 71 | Try 72 | { 73 | 74 | Write-Host "Exiting standby mode for instance: $instanceId in ASG: $ASGName." 75 | Exit-ASStandby -InstanceId $instanceId -AutoScalingGroupName $ASGName -Force 76 | Write-Host "Instance exited standby mode, waiting for it to go into service." 77 | 78 | $instanceState = (Get-ASAutoScalingInstance -InstanceId $instanceId).LifecycleState 79 | Write-Host "Current State: $instanceState" 80 | 81 | $checkCount = 0 82 | 83 | Write-Host "Retry Parameters:" 84 | Write-Host "Maximum Checks: $maxRegistrationCheckCount" 85 | Write-Host "Check Interval: $registrationCheckInterval" 86 | 87 | While ($instanceState -ne "InService" -and $checkCount -le $maxRegistrationCheckCount) 88 | { 89 | $checkCount += 1 90 | 91 | # Wait a bit until we check the status 92 | Write-Host "Waiting for $registrationCheckInterval seconds for instance to be InService" 93 | Start-Sleep -Seconds $registrationCheckInterval 94 | 95 | If ($checkCount -le $maxRegistrationCheckCount) 96 | { 97 | Write-Host "$checkCount/$maxRegistrationCheckCount Attempts" 98 | } 99 | $instanceState = (Get-ASAutoScalingInstance -InstanceId $instanceId).LifecycleState 100 | Write-Host "Current instance state: $instanceState" 101 | } 102 | 103 | If ($instanceState -eq "InService") 104 | { 105 | Write-Host "Instance in service!" 106 | } 107 | Else 108 | { 109 | Write-Error -Message "Instance not in service: $instanceState" 110 | Exit 1 111 | } 112 | } 113 | Catch 114 | { 115 | Write-Error -Message "Failed to exit standby mode." -Exception $_.Exception 116 | Exit 1 117 | } 118 | -------------------------------------------------------------------------------- /AzureDevOps_Get-ReleasesToPromote.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Description 3 | Script used to gather all releases that have been successfully deployed to QA, 4 | but not yet promoted to Pre-Prod or Prod environments. 5 | 6 | .Outputs 7 | "C:\Temp\LatestReleases.html" 8 | #> 9 | 10 | [CmdletBinding()] 11 | Param 12 | ( 13 | [Parameter(Mandatory=$true)] 14 | $PAT, # Personal Access Token 15 | [Parameter(Mandatory=$false)] 16 | $TFSBaseURL 17 | ) 18 | # Base64-encodes the Personal Access Token (PAT) appropriately 19 | # This is required to pass PAT through HTTP header 20 | $script:User = "" # Not needed when using PAT, can be set to anything 21 | $script:Base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $User,$PAT))) 22 | 23 | # Get list of all release definitions 24 | [uri] $script:GetDefinitionsUri = "$TFSBaseURL/_apis/Release/definitions" 25 | 26 | # Invoke the REST call and capture the response 27 | $GetDefinitionsUriResponse = Invoke-RestMethod -Uri $GetDefinitionsUri -Method Get -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 28 | $DefinitionIDs = $GetDefinitionsUriResponse.value.id 29 | 30 | # Create custom object to store output in, that can be used to build HTML report. 31 | $objTemplateObject = New-Object psobject 32 | $objTemplateObject | Add-Member -MemberType NoteProperty -Name DefinitionName -Value $null 33 | $objTemplateObject | Add-Member -MemberType NoteProperty -Name Link -Value $null 34 | 35 | # Create empty array which will become the output object 36 | $objResult = @() 37 | 38 | # Use definition ID's to loop and get latest deployments of each definition 39 | ForEach($DefinitionID in $DefinitionIDs){ 40 | [uri] $GetLatestDeployments = "$TFSBaseURL/_apis/release/deployments?definitionId=" + $DefinitionID + "&api-version=4.0-preview&deploymentStatus=succeeded" 41 | $GetLatestDeploymentsResponse = Invoke-RestMethod -Uri $GetLatestDeployments -Method GET -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 42 | 43 | # Get successful deployments to QA 44 | $Deployments = "" 45 | $Deployments = $GetLatestDeploymentsResponse.value | 46 | Where-Object {$_.releaseEnvironment.name -like "QA*" -AND $_.deploymentStatus -eq "succeeded"} 47 | 48 | # Use first deployment ID in array to pick latest 49 | Try{ 50 | $LatestDeployment = "" 51 | $LatestDeployment = $Deployments[0] 52 | } 53 | Catch{ 54 | # Do nothing if null array 55 | } 56 | 57 | # Use Release ID to check if release is already deployed to Pre-Prod or Prod 58 | $ReleaseId = $LatestDeployment.release.id 59 | [uri] $GetRelease = "$TFSBaseURL/_apis/Release/releases/" + $ReleaseId + "?api-version=4.0-preview" 60 | $GetReleaseResponse = Invoke-RestMethod -Uri $GetRelease -Method GET -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 61 | 62 | # Get active releases only (not abandoned) 63 | $GetReleaseResponse = $GetReleaseResponse | Where-Object {$_.status -eq "active"} 64 | 65 | # Check if deployed to pre-prod or prod yet, and active (not abandoned) 66 | $NoDeployment = "" 67 | $NoDeployment = $GetReleaseResponse.environments | Where-Object {$_.name -like "*PROD*" -AND $_.status -eq "notStarted"} 68 | $NoDeploymentReleaseDefinitionName = "" 69 | $NoDeploymentReleaseDefinitionName = $NoDeployment.releaseDefinition.name 70 | 71 | If($NoDeployment){ 72 | # Write output to Azure Pipeline log 73 | $NoDeploymentReleaseDefinitionName | Select-Object -first 1 74 | $LatestDeployment.release.webAccessUri 75 | 76 | # Create an instance of new object to prepare it with data and later add it to the result array for report 77 | $objTemp = $objTemplateObject | Select-Object * 78 | 79 | # Populate the custom object properties 80 | $objTemp.DefinitionName = $NoDeploymentReleaseDefinitionName | Select-Object -first 1 81 | $objTemp.Link = $LatestDeployment.release.webAccessUri 82 | 83 | # Add temp object to output array and get ready to loop back around 84 | $objResult += $objTemp 85 | } 86 | } 87 | 88 | # Set CSS properties for HTML report 89 | $Header = @" 90 | 95 | "@ 96 | 97 | # Output to HTML file that is sent via email in release definition 98 | $objResult = $objResult | 99 | ConvertTo-Html @{Label="DefinitionName";Expression={$_.DefinitionName}},@{Label="Link";Expression={ "$($_.Link)" }} -Head $Header 100 | Add-Type -AssemblyName System.Web 101 | [System.Web.HttpUtility]::HtmlDecode($objResult) | Out-File "C:\Temp\LatestReleases.html" 102 | -------------------------------------------------------------------------------- /Copy-AzDevOpsBuildDefinition.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param 3 | ( 4 | [Parameter(Mandatory=$true)] 5 | $PAT, # Personal Access Token 6 | [Parameter(Mandatory=$true)] 7 | $DefinitionToCloneID, # ID of "Golden" build definition to clone. 8 | [Parameter(Mandatory=$true)] 9 | $LOB, # Line of business name. Used to reference Git repo source of build definition. 10 | [Parameter(Mandatory=$true)] 11 | $AzureDevOpsProjectURL # https://vsrm.dev.azure.com/{organization}/{project} 12 | ) 13 | 14 | # Base64-encodes the Personal Access Token (PAT) appropriately 15 | # This is required to pass PAT through HTTP header 16 | $script:User = "" # Not needed when using PAT, can be set to anything 17 | $script:Base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $User,$PAT))) 18 | 19 | # Get Definition URI https://docs.microsoft.com/en-us/rest/api/azure/devops/build/definitions/get?view=azure-devops-rest-5.1 20 | [uri] $script:GetDefinitionUri = "$AzureDevOpsProjectURL/_apis/build/definitions/$DefinitionToCloneID`?api-version=5.0" 21 | $GetDefinitionResponse = Invoke-RestMethod -Uri $GetDefinitionUri -Method GET -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 22 | 23 | # Use response to form requst body for new definition 24 | $GetDefinitionResponse.name = "$LOB" # Set new definition name to name of LOB 25 | $GetDefinitionResponse.repository.name = "$LOB" # Set repo name and URL to LOB repo 26 | $GetDefinitionResponse.repository.url = "$AzureDevOpsProjectURL/_git/$LOB" 27 | 28 | # Convert response to JSON to be used in POST body below 29 | $ConvertResponseToRequestBody = $GetDefinitionResponse | ConvertTo-Json -Depth 10 30 | 31 | # Create Definition URI https://docs.microsoft.com/en-us/rest/api/azure/devops/build/definitions/create?view=azure-devops-server-rest-5.0 32 | [uri] $script:CreateDefinitionUri = "$AzureDevOpsProjectURL/_apis/build/definitions?definitionToCloneId=$DefinitionToCloneID&api-version=5.0" 33 | 34 | # Invoke the Create REST call and capture the response 35 | Invoke-RestMethod -Uri $CreateDefinitionUri -Method POST -Body $ConvertResponseToRequestBody -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 36 | -------------------------------------------------------------------------------- /Delete-AzureRepos.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Description 3 | Nukes all Git repos in an Azure DevOps Org. 4 | #> 5 | 6 | $PAT = "" # Personal Access Token 7 | $AzureDevOpsOrgURL = "" 8 | 9 | # Base64-encodes the Personal Access Token (PAT) appropriately 10 | # This is required to pass PAT through HTTP header 11 | $script:User = "" # Not needed when using PAT, can be set to anything 12 | $script:Base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $User,$PAT))) 13 | 14 | # URI to get list of all Projects in Org 15 | [uri] $script:GetProjectsURI = "$AzureDevOpsOrgURL/_apis/projects" 16 | 17 | # Get list of all Projects in Org 18 | $GetProjectsResponse = Invoke-RestMethod -Uri $GetProjectsURI -Method GET -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 19 | $Projects = $GetProjectsResponse.value.name 20 | 21 | # Loop through each project and get repo ID 22 | ForEach($Project in $Projects){ 23 | # URI to get project repos 24 | [uri] $script:GetProjectReposURI = "$AzureDevOpsOrgURL/$Project/_apis/git/repositories?api-version=5.0" 25 | $GetProjectReposResponse = Invoke-RestMethod -Uri $GetProjectReposURI -Method GET -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 26 | $RepoIDs = $GetProjectReposResponse.value.id 27 | 28 | # Loop through each repo ID in project and DELETE 29 | ForEach($RepoID in $RepoIDs){ 30 | If($RepoID){ 31 | "Deleting repo from $Project" 32 | [uri] $script:DeleteRepoURI = "$AzureDevOpsOrgURL/$Project/_apis/git/repositories/$RepoID`?api-version=5.0" 33 | $DeleteRepoResponse = Invoke-RestMethod -Uri $DeleteRepoURI -Method DELETE -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /GAC_RegisterAssemblies.ps1: -------------------------------------------------------------------------------- 1 | # List of assmemblies to be GAC'd 2 | $assemblyDll = @('example.dll') 3 | 4 | # method for adding new assemblies to the GAC 5 | function Add-GacItem([string]$file) { 6 | Begin 7 | { 8 | # see if the Enterprise Services Namespace is registered 9 | if ($null -eq ([AppDomain]::CurrentDomain.GetAssemblies() |? { $_.FullName -eq "System.EnterpriseServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=$publicKeyToken" }) ) { 10 | # register the Enterprise Service .NET library 11 | [System.Reflection.Assembly]::Load("System.EnterpriseServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=$publicKeyToken") | Out-Null 12 | } 13 | 14 | # create a reference to the publish class 15 | $publish = New-Object System.EnterpriseServices.Internal.Publish 16 | } 17 | Process 18 | { 19 | # ensure the file that was provided exists 20 | if ( -not (Test-Path $file -type Leaf) ) { 21 | throw "The assembly '$file' does not exist." 22 | } 23 | 24 | # ensure the file is strongly signed before installing in the GAC 25 | if ( [System.Reflection.Assembly]::LoadFile( $file ).GetName().GetPublicKey().Length -eq 0) { 26 | throw "The assembly '$file' must be strongly signed." 27 | } 28 | 29 | # install the assembly in the GAC 30 | Write-Output "Installing: $assembly" 31 | $publish.GacInstall( $file ) 32 | } 33 | } 34 | 35 | # method for removing assemblies from the GAC 36 | function Remove-GacItem([string]$file) { 37 | Begin 38 | { 39 | # see if the Enterprise Services Namespace is registered 40 | if ($null -eq ([AppDomain]::CurrentDomain.GetAssemblies() |? { $_.FullName -eq "System.EnterpriseServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=$publicKeyToken" }) ) { 41 | # register the Enterprise Service .NET library 42 | [System.Reflection.Assembly]::Load("System.EnterpriseServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=$publicKeyToken") | Out-Null 43 | } 44 | 45 | # create a reference to the publish class 46 | $publish = New-Object System.EnterpriseServices.Internal.Publish 47 | } 48 | Process 49 | { 50 | # ensure the file that was provided exists 51 | if ( -not (Test-Path $file -type Leaf) ) { 52 | throw "The assembly '$file' does not exist." 53 | } 54 | 55 | # ensure the file is strongly signed before installing in the GAC 56 | if ( [System.Reflection.Assembly]::LoadFile( $file ).GetName().GetPublicKey().Length -eq 0) { 57 | throw "The assembly '$file' must be strongly signed." 58 | } 59 | 60 | # install the assembly in the GAC 61 | Write-Output "UnInstalling: $file" 62 | $publish.GacRemove( $file ) 63 | } 64 | } 65 | 66 | foreach($file in $assemblyDll) 67 | { 68 | 69 | Write-Host $file 70 | $currentDirectory = Get-Location 71 | $file = $currentDirectory.Path + "\" + $file 72 | 73 | Write-Host "UnRegistering the Assembly: '$file'" 74 | Remove-GacItem $file 75 | 76 | Write-Host "Registering the Assembly: '$file'" 77 | Add-GacItem $file 78 | 79 | 80 | } 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /GenerateAzureSASKey.ps1: -------------------------------------------------------------------------------- 1 | $storageAccountName = "" 2 | $storageAccountKey = "" 3 | $container = "" 4 | 5 | $context = New-AzureStorageContext -StorageAccountName $storageAccountName -StorageAccountKey $storageAccountKey 6 | $sas = New-AzureStorageContainerSASToken -Name $container -Permission rl -Context $context -ExpiryTime ([DateTime]::UtcNow.AddDays(7)) 7 | Write-Host "$($context.BlobEndPoint)$($container)$($sas)" 8 | -------------------------------------------------------------------------------- /Get-InstalledSoftware.ps1: -------------------------------------------------------------------------------- 1 | Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | 2 | Format-Table –AutoSize > D:\temp\installed_software.txt -------------------------------------------------------------------------------- /Get-IssuesWorkCompleted.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Description 3 | Script used to gather all Issues with linked tasks, then sum the amount of completed work in the linked tasks by A and B Teams. 4 | 5 | Gets list of Issues with tasks with shared query. 6 | 7 | .Outputs 8 | "C:\Temp\IssuesWorkCompleted.html" that is sent via email in release definition 9 | #> 10 | 11 | [CmdletBinding()] 12 | Param 13 | ( 14 | [Parameter(Mandatory=$true)] 15 | $PAT, # Personal Access Token 16 | [Parameter(Mandatory=$false)] 17 | $AzureDevOpsBaseURL 18 | ) 19 | 20 | # https://docs.microsoft.com/en-us/azure/devops/integrate/concepts/rest-api-versioning?view=azure-devops 21 | # Specify api version to prevent breaking changes after upgrdades 22 | $apiVersion = "3.0" 23 | 24 | # Base64-encodes the Personal Access Token (PAT) appropriately 25 | # This is required to pass PAT through HTTP header 26 | $script:User = "" # Not needed when using PAT, can be set to anything 27 | $script:Base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $User,$PAT))) 28 | 29 | # Create custom object to store output in, that can be used to build HTML report. 30 | $objTemplateObject = New-Object psobject 31 | $objTemplateObject | Add-Member -MemberType NoteProperty -Name WIID -Value $null 32 | $objTemplateObject | Add-Member -MemberType NoteProperty -Name WIName -Value $null 33 | $objTemplateObject | Add-Member -MemberType NoteProperty -Name WICreatedDate -Value $null 34 | $objTemplateObject | Add-Member -MemberType NoteProperty -Name WICreatedBy -Value $null 35 | $objTemplateObject | Add-Member -MemberType NoteProperty -Name WIClosedDate -Value $null 36 | $objTemplateObject | Add-Member -MemberType NoteProperty -Name WITeam -Value $null 37 | $objTemplateObject | Add-Member -MemberType NoteProperty -Name WIAreaPath -Value $null 38 | $objTemplateObject | Add-Member -MemberType NoteProperty -Name PercentA -Value $null 39 | $objTemplateObject | Add-Member -MemberType NoteProperty -Name PercentB -Value $null 40 | $objTemplateObject | Add-Member -MemberType NoteProperty -Name ATeam -Value $null 41 | $objTemplateObject | Add-Member -MemberType NoteProperty -Name BTeam -Value $null 42 | 43 | # Create empty array which will become the output object 44 | $objResult = @() 45 | 46 | # Get all work items using shared query "All Issues with Closed Tasks" 47 | [uri] $GetWorkItemQueryURI = "$AzureDevOpsBaseURL/_apis/wit/wiql/d32f77bd-2ed5-4c23-aac8-002294f34074" + "?api-version=$apiVersion" 48 | $GetWorkItemQueryResponse = Invoke-RestMethod -Uri $GetWorkItemQueryURI -Method GET -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 49 | 50 | # Get Issues 51 | $Issues = $GetWorkItemQueryResponse.workItemRelations.source.url 52 | 53 | # Get rid of dupes 54 | $Issues = $Issues | Select-Object -Unique 55 | 56 | # Use AzureDevOps Team "A" to identify A team members 57 | [uri] $GetTeamURI = "$AzureDevOpsBaseURL/_apis/projects/PROJECTNAME/teams/A/members" + "?api-version=$apiVersion" 58 | $GetTeamURIResponse = Invoke-RestMethod -Uri $GetTeamURI -Method GET -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 59 | $BTeamMembers = $GetTeamURIResponse.value.uniquename -replace "DOMAINNAME\\", "" 60 | 61 | ForEach($Issue in $Issues){ 62 | $GetIssueWorkItemResponse = Invoke-RestMethod -Uri "$Issue`?api-version=$apiVersion`&`$expand=relations" -Method GET -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 63 | 64 | # Create an instance of new object to prepare it with data and later add it to the result array for report 65 | $objTemp = $objTemplateObject | Select-Object * 66 | 67 | # Get related tasks 68 | $relatedWorkItems = $GetIssueWorkItemResponse.relations | Where-Object {$_.rel -like "System.LinkTypes*" -OR $_.rel -like "Microsoft.VSTS*"} 69 | $relatedWorkItems = $relatedWorkItems.url 70 | If($relatedWorkItems){ 71 | ForEach($workItem in $relatedWorkItems){ 72 | $GetRelatedWorkItemResponseURI = $workItem + "?api-version=$apiVersion" 73 | $GetRelatedWorkItemResponse = Invoke-RestMethod -Uri $GetRelatedWorkItemResponseURI -Method GET -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 74 | If($GetRelatedWorkItemResponse.Fields.'System.WorkItemType' -eq "Task"){ 75 | $relatedTask = $workItem 76 | $GetRelatedTaskURI = $relatedTask + "?api-version=$apiVersion" 77 | $GetRelatedTaskResponse = Invoke-RestMethod -Uri $GetRelatedTaskURI -Method GET -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 78 | 79 | # Figure out which team is assigned the task, then add completed hours 80 | $AssignedTo = $GetRelatedTaskResponse.fields.'System.AssignedTo' 81 | 82 | If(!($AssignedTo)){ 83 | "Task not assigned" 84 | } 85 | Else{ 86 | # Remove First, Last, and Domain name from AssignedTo, we only want username 87 | $AssignedTo = ($AssignedTo -split "\", 2, "simplematch")[1] 88 | $AssignedTo = $AssignedTo.TrimEnd(">") 89 | 90 | "Assigned To: " + $AssignedTo 91 | If($AssignedTo -in $BTeamMembers){ 92 | $objTemp.BTeam += $GetRelatedTaskResponse.fields.'Microsoft.VSTS.Scheduling.CompletedWork' 93 | "A Team Hours: " + $objTemp.BTeam 94 | } 95 | Else{ 96 | $objTemp.ATeam += $GetRelatedTaskResponse.fields.'Microsoft.VSTS.Scheduling.CompletedWork' 97 | "B Team Hours: " + $objTemp.ATeam 98 | } 99 | } 100 | } 101 | } 102 | # Only populate object if there is completed work > 0 103 | If($objTemp.ATeam -gt 0 -OR $objTemp.BTeam -gt 0){ 104 | $objTemp.WIID = $GetIssueWorkItemResponse.id 105 | $objTemp.WIName = $GetIssueWorkItemResponse.fields.'System.Title' 106 | $objTemp.WIName = $objTemp.WIName | Select-Object -First 50 107 | 108 | $WICreatedDate = $GetIssueWorkItemResponse.fields.'System.CreatedDate' 109 | $objTemp.WICreatedDate = ($WICreatedDate -split "T", 2, "simplematch")[0] 110 | $objTemp.WICreatedBy = $GetIssueWorkItemResponse.fields.'System.CreatedBy' 111 | $WIClosedDate = $GetIssueWorkItemResponse.fields.'Microsoft.VSTS.Common.ClosedDate' 112 | $objTemp.WIClosedDate = ($WIClosedDate -split "T", 2, "simplematch")[0] 113 | $objTemp.WIAreaPath = $GetIssueWorkItemResponse.fields.'System.AreaPath' 114 | $objTemp.WITeam = $GetIssueWorkItemResponse.fields.'Custom.Team' 115 | 116 | # Find percentages between A and B 117 | $Total = $objTemp.ATeam + $objTemp.BTeam 118 | $objTemp.PercentA = ($objTemp.ATeam/$Total * 100) 119 | $objTemp.PercentB = ($objTemp.BTeam/$Total * 100) 120 | } 121 | } 122 | Else{ 123 | "No related work items" 124 | continue # Move to next iteration in ForEach of Issues 125 | } 126 | # All report fields populated for Issue, add temp object to output array and get ready to loop back around 127 | $objResult += $objTemp 128 | } 129 | 130 | # Output Work Item ID, Work Item Name, Team, and Completed Work to 2nd fragment of HTML file 131 | $Fragment1 = $objResult | 132 | Select-Object -Property @{n="Issue Work Item ID";e={$_.WIID}},@{n="Issue Work Item Name";e={$_.WIName}},@{n="Created Date";e={$_.WICreatedDate}},@{n="Created By";e={$_.WICreatedBy}},@{n="Closed Date";e={$_.WIClosedDate}},@{n="Area Path";e={$_.WIAreaPath}},@{n="Team";e={$_.WITeam}},@{n="% A";e={$_.PercentA}},@{n="% B";e={$_.PercentB}},@{n="Total Hours A Team";e={$_.ATeam}},@{n="Total Hours B Team";e={$_.BTeam}} | 133 | Sort-Object -Property WIID -Descending | 134 | ConvertTo-Html -Fragment 135 | 136 | # Insert boostrap classes and required thead and tbody for sort 137 | $Fragment1 = $Fragment1 -replace '','
' 138 | $Fragment1 = $Fragment1 -replace '','' 139 | $Fragment1 = $Fragment1 -replace '
','' 140 | $Fragment1 = $Fragment1 -replace '','' 141 | $Fragment1 = $Fragment1 -replace '','' 142 | $Fragment1 = $Fragment1 -replace '','' 143 | 144 | # Add Bootstrap and Bootstrap tables 145 | $Precontent='Issue Work Completed by Team' 146 | $Postcontent='' 147 | 148 | $ConvertedHTML = ConvertTo-HTML -Body "$Fragment1" -Head $Precontent -PostContent $Postcontent 149 | 150 | # Find and replace necessary elements in converted HTML that are outside of fragments 151 | $ConvertedHTML = $ConvertedHTML -replace '', '' 152 | $ConvertedHTML = $ConvertedHTML -replace '', '' 153 | $ConvertedHTML = $ConvertedHTML -replace '', '' # remove empty rows 154 | 155 | $ConvertedHTML | 156 | Out-File "C:\Temp\IssuesWorkCompleted.html" -------------------------------------------------------------------------------- /Get-UpdatedAppForAzDevOpsBuild.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Description 3 | Script to determine which app should be built. 4 | .Outputs 5 | "App*Updated" variable set that can be used in subsequent build task conditions. 6 | e.g. and(succeeded(), eq(variables['App1Updated'], 'True')) 7 | #> 8 | 9 | # Get all files that changed 10 | # https://git-scm.com/docs/git-diff 11 | $EditedFiles = git diff HEAD HEAD~ --name-only 12 | 13 | # Check each file that was changed and set variable 14 | $EditedFiles | ForEach-Object { 15 | Switch -Wildcard ($_ ) { 16 | "App1/*" { 17 | Write-Host "App 1 changed" 18 | Write-Host "##vso[task.setvariable variable=App1Updated]True" 19 | } 20 | "App2/*" { 21 | Write-Host "App 2 changed" 22 | Write-Host "##vso[task.setvariable variable=App2Updated]True" 23 | } 24 | "App3/*" { 25 | Write-Host "App 3 changed" 26 | Write-Host "##vso[task.setvariable variable=App3Updated]True" 27 | } 28 | # Add the rest of the App path filters here 29 | } 30 | } -------------------------------------------------------------------------------- /IIS_Deploy.psm1: -------------------------------------------------------------------------------- 1 | #region IIS functions 2 | 3 | Function Install-DotNetCoreHostingBundle { 4 | <# 5 | .Synopsis 6 | Install dotnet core hosting bundle (if not already installed) 7 | 8 | .Description 9 | Checks if dotnet core hosting bundle is already installed. If it is, do nothing. 10 | If it is not, use dotnet-hosting-2.1.6-win.exe to install the bundle quietly. 11 | 12 | After the install, resets IIS to pick up changes made to system PATH en variable 13 | 14 | .Example 15 | Install-DotNetCoreHostingBundle 16 | #> 17 | $vm_dotnet_core_hosting_module = Get-WebGlobalModule | where-object { $_.name.ToLower() -eq "aspnetcoremodule" } 18 | If (!$vm_dotnet_core_hosting_module){ 19 | ".Net core hosting module is not installed." 20 | "Starting install of dotnet-hosting-2.1.6..." 21 | .\dotnet-hosting-2.1.6-win.exe /install /quiet /norestart 22 | 23 | # Restarting IIS picks up a change to the system PATH, which is an environment variable, made by the installer. 24 | "Restarting IIS to pick up changes to system PATH..." 25 | cmd.exe /c "net stop was /y" 26 | cmd.exe /c "net start w3svc" 27 | } 28 | Else{ 29 | ".Net core hosting bundle already installed." 30 | } 31 | } 32 | 33 | Function Install-IISURLRewrite { 34 | <# 35 | .Synopsis 36 | Install IIS URL Rewrite module (if not already installed) 37 | 38 | .Description 39 | Checks if IIS URL Rewrite module is already installed, if it is, do nothing. 40 | If it is not, use WebPlatformInstaller_amd64_en-US.msi to install the quietly. 41 | 42 | .Example 43 | Install-IISURLRewrite 44 | #> 45 | $vm_URL_Rewrite_module = Get-WebGlobalModule | where-object { $_.name.ToLower() -eq "RewriteModule" } 46 | If (!$vm_URL_Rewrite_module){ 47 | "URL Rewrite module is not installed." 48 | "Starting install of urlrewrite2.exe..." 49 | Start-Process './WebPlatformInstaller_amd64_en-US.msi' '/qn' -PassThru | Wait-Process 50 | Set-Location 'C:/Program Files/Microsoft/Web Platform Installer'; .\WebpiCmd.exe /Install /Products:'UrlRewrite2' /AcceptEULA 51 | } 52 | Else{ 53 | "URL Rewrite module already installed." 54 | } 55 | } 56 | 57 | Function New-AppPool { 58 | <# 59 | .Synopsis 60 | Creates new IIS Application Pool (if it does not exist) 61 | 62 | .Description 63 | Checks if app pool exists, if it does do nothing. 64 | If it does not, create it. 65 | 66 | .Example 67 | New-AppPool -iisAppPoolName "TestAppPoolName" -iisIdentity "DOMAIN\FakeUser" 68 | #> 69 | [CmdletBinding()] 70 | Param 71 | ( 72 | [Parameter(Mandatory)] 73 | $iisAppPoolName, 74 | [Parameter(Mandatory)] 75 | $iisIdentity, 76 | [Parameter(Mandatory=$false)] 77 | $iisAppPoolDotNetVersion = "", 78 | [Parameter(Mandatory=$false)] 79 | $iisAppPoolManagedPipelineMode = "" 80 | ) 81 | 82 | # WebAdministration Module Required 83 | Import-Module WebAdministration 84 | 85 | # Navigate to the app pools root 86 | Set-Location IIS:\AppPools 87 | 88 | # Check if the app pool exists 89 | If (!(Test-Path $iisAppPoolName -pathType container)) { 90 | # Create the app pool 91 | "App pool does not exist." 92 | "Creating $iisAppPoolName app pool..." 93 | $appPool = New-Item $iisAppPoolName 94 | $appPoolPath = "IIS:\AppPools\"+ $appPool.name 95 | "Resetting IIS to avoid locks on applicationHost.config" 96 | iisreset /restart 97 | Set-ItemProperty $appPoolPath -Name managedRuntimeVersion -Value $iisAppPoolDotNetVersion 98 | "Set managedRuntimeVersion" 99 | "Resetting IIS to avoid locks on applicationHost.config..." 100 | iisreset /restart 101 | Set-ItemProperty $appPoolPath -Name processModel -Value @{userName="$iisIdentity";password="$iisIdentityPassword";identitytype=3} -Force 102 | "Set app pool to run as: $iisIdentity " 103 | 104 | # Default Managed Pipeline Mode is Integrated. 105 | # If param $iisAppPoolManagedPipelineMode is set to Classic, set it. 106 | If($iisAppPoolManagedPipelineMode -eq "Classic"){ 107 | "Setting Managed Pipeline Mode to Classic..." 108 | $iisAppPool = Get-Item IIS:\AppPools\$iisAppPoolName 109 | $iisAppPool.managedPipelineMode="Classic" 110 | $iisAppPool | set-item 111 | } 112 | "App pool created." 113 | } 114 | Else{ 115 | "App pool already exists." 116 | } 117 | } 118 | 119 | Function New-WindowsAuthWebApp { 120 | <# 121 | .Synopsis 122 | Creates new IIS Application that uses Windows Auth (if it does not exist) 123 | 124 | .Description 125 | Checks if app exists, if it does, do nothing. 126 | If it does not, create it and enable Windows auth. 127 | 128 | .Example 129 | New-WindowsAuthWebApp -iisAppPoolName "TestAppPoolName" -iisAppName "TestAppName" -directoryPath "C:\Inetpub" 130 | #> 131 | [CmdletBinding()] 132 | Param 133 | ( 134 | [Parameter(Mandatory)] 135 | $iisAppPoolName, 136 | [Parameter(Mandatory)] 137 | $iisAppName, 138 | [Parameter(Mandatory)] 139 | $directoryPath 140 | ) 141 | # WebAdministration Module Required 142 | Import-Module WebAdministration 143 | 144 | # Navigate to the sites root 145 | Set-Location "IIS:\Sites\Default Web Site" 146 | 147 | # Check if the app exists 148 | If ( -Not (Get-WebApplication $iisAppName) ) { 149 | # Create the app 150 | "App does not exist." 151 | "Creating $iisAppName..." 152 | "Resetting IIS to avoid locks on applicationHost.config..." 153 | iisreset /restart 154 | New-WebApplication $iisAppName -ApplicationPool $iisAppPoolName -PhysicalPath $directoryPath -Force 155 | "App created." 156 | "Disabling anonymous authentication..." 157 | Set-WebConfigurationProperty -filter /system.webServer/security/authentication/anonymousAuthentication -name enabled -value false -PSPath IIS:\\ -location "Default Web Site/$iisAppName" 158 | "Enabling Windows Auth.." 159 | Set-WebConfigurationProperty -filter /system.webServer/security/authentication/windowsAuthentication -name enabled -value true -PSPath IIS:\\ -location "Default Web Site/$iisAppName" 160 | } 161 | Else{ 162 | "App already exists." 163 | } 164 | } 165 | 166 | Function New-AnonAuthWebApp { 167 | <# 168 | .Synopsis 169 | Creates new IIS Application that uses Anonymous Auth (if it does not exist) 170 | 171 | .Description 172 | Checks if app exists, if it does, do nothing. 173 | If it does not, create it and enable Anonymous auth. 174 | 175 | .Example 176 | New-AnonAuthWebApp -iisAppPoolName "TestAppPoolName" -iisAppName "TestAppName" -directoryPath "C:\Inetpub\TestAppName" 177 | 178 | #> 179 | Param 180 | ( 181 | [Parameter(Mandatory)] 182 | $iisAppPoolName, 183 | [Parameter(Mandatory)] 184 | $iisAppName, 185 | [Parameter(Mandatory)] 186 | $directoryPath 187 | ) 188 | # WebAdministration Module Required 189 | Import-Module WebAdministration 190 | 191 | # Navigate to the sites root 192 | Set-Location "IIS:\Sites\Default Web Site" 193 | 194 | # Check if the app exists 195 | If ( -Not (Get-WebApplication $iisAppName) ) { 196 | # Create the app 197 | "App does not exist." 198 | "Creating $iisAppName..." 199 | "Resetting IIS to avoid locks on applicationHost.config..." 200 | iisreset /restart 201 | New-WebApplication $iisAppName -ApplicationPool $iisAppPoolName -PhysicalPath $directoryPath -Force 202 | "App created." 203 | "Enabling anonymous authentication..." 204 | Set-WebConfigurationProperty -filter /system.webServer/security/authentication/anonymousAuthentication -name enabled -value true -PSPath IIS:\\ -location "Default Web Site/$iisAppName" 205 | "Disabling Windows Auth.." 206 | Set-WebConfigurationProperty -filter /system.webServer/security/authentication/windowsAuthentication -name enabled -value false -PSPath IIS:\\ -location "Default Web Site/$iisAppName" 207 | } 208 | Else{ 209 | "App already exists." 210 | } 211 | } 212 | 213 | Function New-Website { 214 | <# 215 | .Synopsis 216 | Creates new IIS Website that uses Anonymous Auth (if it does not exist) 217 | 218 | .Description 219 | Checks if website exists, if it does, do nothing. 220 | If it does not, create it and enable Anonymous auth. 221 | 222 | .Example 223 | New-Website -iisAppPoolName "TestAppPoolName" -iisAppName "TestAppName" -directoryPath "C:\Inetpub\TestAppName" -iisPort "80" -iisPortSSL "443" 224 | 225 | #> 226 | [CmdletBinding()] 227 | Param 228 | ( 229 | [Parameter(Mandatory)] 230 | $iisAppPoolName, 231 | [Parameter(Mandatory)] 232 | $iisAppName, 233 | [Parameter(Mandatory)] 234 | $directoryPath, 235 | [Parameter(Mandatory)] 236 | $iisPort, 237 | [Parameter(Mandatory)] 238 | $iisPortSSL 239 | ) 240 | # WebAdministration Module Required 241 | Import-Module WebAdministration 242 | 243 | # Navigate to the sites root 244 | Set-Location "IIS:\Sites" 245 | 246 | # Check if the app exists 247 | If ( -Not (Get-Website $iisAppName) ) { 248 | # Create the app 249 | "Website does not exist." 250 | "Creating $iisAppName..." 251 | "Resetting IIS to avoid locks on applicationHost.config..." 252 | iisreset /restart 253 | 254 | # Create Website 255 | New-Website -Name $iisAppName -ApplicationPool $iisAppPoolName -PhysicalPath $directoryPath 256 | 257 | # Add http and https bindings if port for https is passed 258 | If($iisPortSSL -ne "False"){ 259 | "Getting IP address for binding..." 260 | $iisIPAddress=((ipconfig | findstr [0-9].\.)[0]).Split()[-1] 261 | "IP is: $iisIPAddress" 262 | "Adding https binding..." 263 | New-WebBinding -Name $iisAppName -Protocol https -Port $iisPortSSL -IPAddress $iisIPAddress -SslFlags 0 264 | "Adding cert to https binding..." 265 | $CertCN = "$env:ComputerName" 266 | $Thumbprint = (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object {$_.Subject -like "*$CertCN*"}).Thumbprint; 267 | $httpsBinding = Get-WebBinding -Name $iisAppName -Protocol "https" 268 | $httpsBinding.AddSslCertificate($Thumbprint, "my") 269 | "Removing default http binding that's added when creating site..." 270 | Get-WebBinding -Name $iisAppName -Protocol http | Remove-WebBinding 271 | "Adding new http binding..." 272 | New-WebBinding -Name $iisAppName -Protocol http -Port $iisPort -IPAddress $iisIPAddress -SslFlags 0 273 | } 274 | Else{ 275 | "Adding http binding..." 276 | New-WebBinding -Name $iisAppName -Protocol http -Port $iisPort -IPAddress $iisIPAddress -SslFlags 0 277 | } 278 | "App created." 279 | 280 | "Disabling anonymous authentication..." 281 | Set-WebConfigurationProperty -filter /system.webServer/security/authentication/anonymousAuthentication -name enabled -value false -PSPath IIS:\\ -location "$iisAppName" 282 | "Enabling Windows Auth.." 283 | Set-WebConfigurationProperty -filter /system.webServer/security/authentication/windowsAuthentication -name enabled -value true -PSPath IIS:\\ -location "$iisAppName" 284 | } 285 | Else{ 286 | "Website already exists." 287 | } 288 | } 289 | #endregion IIS functions -------------------------------------------------------------------------------- /IIS_SetPropertiesForFasterStartup.ps1: -------------------------------------------------------------------------------- 1 | #Sets IIS properties for faster load times 2 | 3 | #Load IIS Module if not loaded already 4 | Function WebAdministration-VerifyModule 5 | { 6 | If ( ! (Get-module WebAdministration )) 7 | { 8 | Try 9 | { 10 | Import-Module WebAdministration 11 | } 12 | Catch 13 | { 14 | Write-Host "Exception Type: $($_.Exception.GetType().FullName)" 15 | Write-Host "Exception Message: $($_.Exception.Message)" 16 | } 17 | } 18 | $message = "The module WebAdministration verified at (" + (Get-Date).ToString('yyyy.MM[MMM].dd HH:mm:ss zzz') + ")." 19 | Write-Host $message 20 | return $true 21 | } 22 | 23 | Function SetIISProperties-AppPools 24 | { 25 | Try 26 | { 27 | #Loop through each app pool 28 | dir IIS:\Sites | ForEach-Object { $appPool = $_.applicationPool 29 | #set start mode to always running 30 | Set-ItemProperty IIS:\AppPools\$appPool -name startMode -value "alwaysrunning" 31 | #set idle timeout to 0 seconds 32 | Set-ItemProperty IIS:\AppPools\$appPool -name processModel.idleTimeout -value "0" 33 | write-output "$appPool app pool set to startMode:always running, idle timeout: 0 seconds" 34 | } 35 | } 36 | Catch 37 | { 38 | Write-Host "Exception Type: $($_.Exception.GetType().FullName)" 39 | Write-Host "Exception Message: $($_.Exception.Message)" 40 | } 41 | } 42 | 43 | Function SetIISProperties-Sites 44 | { 45 | Try 46 | { 47 | #Loop through each site 48 | dir IIS:\Sites | ForEach-Object { $siteName = $_.Name 49 | #set preLoadEnabled to true 50 | Set-WebConfigurationProperty "`/system.applicationHost`/sites`/site[@name=`"$siteName`"]/application" -Name "preloadEnabled" -Value "true" -PSPath IIS:\ 51 | write-output "$siteName site set to preloadEnabled:True" 52 | } 53 | } 54 | Catch 55 | { 56 | Write-Host "Exception Type: $($_.Exception.GetType().FullName)" 57 | Write-Host "Exception Message: $($_.Exception.Message)" 58 | } 59 | } 60 | 61 | #Verify module then run functions 62 | Try 63 | { 64 | If (WebAdministration-VerifyModule) 65 | { 66 | SetIISProperties-AppPools 67 | SetIISProperties-Sites 68 | } 69 | Else 70 | { 71 | Write-Error "Module: WebAdministration did not load!" 72 | } 73 | } 74 | Catch 75 | { 76 | Write-Error "Something wrong with Module: WebAdministration" 77 | } 78 | 79 | Exit 0 80 | -------------------------------------------------------------------------------- /JBoss_FTPUploadLogs.ps1: -------------------------------------------------------------------------------- 1 | # Uploads desired logs to FTP site 2 | # Requires PowerShell V 3.0, 7-Zip 3 | 4 | #UI Settings 5 | $a = (Get-Host).UI.RawUI 6 | $b = $a.WindowSize 7 | $b.Width = 60 8 | $b.Height = 30 9 | $a.WindowSize = $b 10 | 11 | [int]$serverNumber = read-host "Upload logs to FTP`n1-Grp-Test`n2-iGrp-Test`n3-Grp-Stage`n4-iGrp-Stage`n5-Grp-Production`n6-iGrp-Production`nSelect number" 12 | 13 | switch($serverNumber){ 14 | 15 | #Grp-Test 16 | 1 {$Dir0 = "\\$machineName\$drive\$JBossDir\server\$node\log\*" 17 | $Dir1 = "\\$machineName\$drive\$JBossDir\server\$node\log\*" 18 | $Dir2 = "\\$machineName\$drive\logs\*" 19 | $Environment = "Test" 20 | $indOrGroup = "Grp"} 21 | #Ind-Test 22 | 2 {$Dir0 = "\\$machineName\$drive\$JBossDir\server\$node\log\*" 23 | $Dir1 = "\\$machineName\$drive\$JBossDir\server\$node\log\*" 24 | $Dir2 = "\\$machineName\$drive\logs\*" 25 | $Environment = "Test" 26 | $indOrGroup = "Ind"} 27 | #Note-Express logs from stage/prod include both environments 28 | #Grp-Stage 29 | 3 {$Dir0 = "\\$machineName\$drive\$JBossDir\server\$node\log\*" 30 | $Dir1 = "\\$machineName\$drive\$JBossDir\server\$node\log\*" 31 | $Dir2 = "\\$machineName\$drive\logs\*" 32 | $Dir3 = "\\$machineName\$drive\logs\*" 33 | $Environment = "Stage" 34 | $indOrGroup = "Grp"} 35 | #Ind-Stage 36 | 4 {$Dir0 = "\\$machineName\$drive\$JBossDir\server\$node\log\*" 37 | $Dir1 = "\\$machineName\$drive\$JBossDir\server\$node\log\*" 38 | $Dir2 = "\\$machineName\$drive\logs\*" 39 | $Dir3 = "\\$machineName\$drive\logs\*" 40 | $Environment = "Stage" 41 | $indOrGroup = "Ind"} 42 | #Grp-Production 43 | 5 {$Dir0 = "\\$machineName\$drive\$JBossDir\server\$node\log\*" 44 | $Dir1 = "\\$machineName\$drive\$JBossDir\server\$node\log\*" 45 | $Dir2 = "\\$machineName\$drive\$JBossDir\server\$node\log\*" 46 | $Dir3 = "\\$machineName\$drive\$JBossDir\server\$node\log\*" 47 | $Dir4 = "\\$machineName\$drive\logs\*" 48 | $Dir5 = "\\$machineName\$drive\logs\*" 49 | $Environment = "Production" 50 | $indOrGroup = "Grp"} 51 | #Ind Production 52 | 6 {$Dir0 = "\\$machineName\$drive\$JBossDir\server\$node\log\*" 53 | $Dir1 = "\\$machineName\$drive\$JBossDir\server\$node\log\*" 54 | $Dir2 = "\\$machineName\$drive\$JBossDir\server\$node\log\*" 55 | $Dir3 = "\\$machineName\$drive\$JBossDir\server\$node\log\*" 56 | $Dir4 = "\\$machineName\$drive\logs\*" 57 | $Dir5 = "\\$machineName\$drive\logs\*" 58 | $Environment = "Production" 59 | $indOrGroup = "Ind"} 60 | default {"Directory could not be determined."} 61 | } 62 | 63 | # Copy log files to network drive and zip before upload. 64 | # Delete and recreate folders so previously uploaded files are not included 65 | Remove-Item \\example\Logs\Dir* -Recurse -Force 66 | New-Item -ItemType Directory\example\Logs\Dir0 67 | New-Item -ItemType Directory\example\Logs\Dir1 68 | New-Item -ItemType Directory\example\Logs\Dir2 69 | if($Environment -eq "Production" -OR $Environment -eq "Stage"){ 70 | New-Item -ItemType Directory\example\Logs\Dir3} 71 | if($Environment -eq "Production"){ 72 | New-Item -ItemType Directory\example\Logs\Dir4 73 | New-Item -ItemType Directory\example\Logs\Dir5} 74 | 75 | # Mulitple folders required because copy-item forced to overwrite files (no dupes allowed) 76 | Copy-Item $Dir0 \\example\Logs\Dir0 -Force -Recurse 77 | Copy-Item $Dir1 \\example\Logs\Dir1 -Force -Recurse 78 | Copy-Item $Dir2 \\example\Logs\Dir2 -Force -Recurse 79 | 80 | # Staging has 2 JBoss nodes 2 express/ Production has 4 JBoss Nodes and 2 express 81 | If($Environment -eq "Production" -or $Environment -eq "Stage"){ 82 | Copy-Item $Dir3 \\example\Logs\Dir3 -Force -Recurse 83 | } 84 | If($Environment -eq "Production"){ 85 | Copy-Item $Dir4 \\example\Logs\Dir4 -Force -Recurse 86 | Copy-Item $Dir5 \\example\Logs\Dir5 -Force -Recurse 87 | } 88 | 89 | # 7-Zip required to zip logs, make sure it's installed 90 | if(-not (test-path "$env:ProgramFiles\7-Zip\7z.exe")) 91 | { 92 | throw "$env:ProgramFiles\7-Zip\7z.exe needed" 93 | } 94 | # Set Alias for 7-zip to sz 95 | set-alias sz "$env:ProgramFiles\7-Zip\7z.exe" 96 | 97 | # Directory of logs to zip 98 | $filePath = "\\example\Repository\logs" 99 | $logs = Get-ChildItem -Recurse -Path $filePath | 100 | Where-Object { $_.Extension -eq ".log" } 101 | 102 | # Place new logs.zip in \example\Logs\ 103 | foreach ($file in $logs) 104 | { 105 | sz a -t7z "\example\Repository\Logs" "\example\Logs" 106 | } 107 | 108 | # ftp server info 109 | $ftp = "" 110 | $user = "" 111 | $pass = "" 112 | 113 | $webclient = New-Object System.Net.WebClient 114 | $webclient.Credentials = New-Object System.Net.NetworkCredential($user,$pass) 115 | 116 | # Uploads logs.zip to FTP 117 | foreach($item in (dir "\example\" "*.7z")){ 118 | "Uploading $item..." 119 | $uri = New-Object System.Uri($ftp+$item.Name) 120 | $webclient.UploadFile($uri, $item.FullName) 121 | } 122 | 123 | [int]$sendEmail = Read-Host "Send email to Bob? (1) Yes (2) No" 124 | 125 | if($sendEmail -eq 1) 126 | { 127 | Send-MailMessage -To "Bob@Bob.com" -SmtpServer mail.bob.com -Subject "$indOrGroup - $Environment logs have been uploaded to FTP" 128 | } 129 | -------------------------------------------------------------------------------- /O365_UndeliverableMessageExport.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | X Mailbox Undeliverable message export and filter 4 | .DESCRIPTION 5 | This script uses ExportOSCEXOEmailMessage PowerShell module (in addition to the Exchange Web API) to connect to the O365 X mailbox, 6 | search for emails according to X criteria during the last week, exports them, 7 | filters the emails for email addresses and saves them to undeliverableEmailList.txt, then sends email with attachment 8 | .OUTPUTS 9 | undeliverableEmailList.txt 10 | .NOTES 11 | Prerequisites: 12 | Exchange Web API https://www.microsoft.com/en-us/download/details.aspx?id=35371 13 | Export-OSCEXOEmailMessage module https://gallery.technet.microsoft.com/office/Export-Email-Messages-from-1419bbe9 14 | #> 15 | 16 | Import-Module "ExportOSCEXOEmailMessage.psm1" 17 | 18 | # Set directory to store emails and save output 19 | $emailDir = "C:\temp\emails" 20 | 21 | # Start WinRM service for remoting (not running by default) 22 | Get-Service "Winrm" | Start-Service 23 | 24 | # Connect to O365 25 | # Create secure string for credentials so prompt for get-credential isn't required 26 | $username = "x@x.com" 27 | $password = '' 28 | $secstr = New-Object -TypeName System.Security.SecureString 29 | $password.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)} 30 | $cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr 31 | $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell/ -Credential $cred -Authentication Basic -AllowRedirection 32 | Import-PSSession $Session 33 | 34 | # Connect to OSCEXO 35 | Connect-OSCEXOWebService -Credential $cred 36 | 37 | # Create a new search folder using start and end dates; range of last 5 days. 38 | $EndDate = Get-Date -format G 39 | $StartDateNoFormat = (Get-Date).AddDays(-5) 40 | $StartDate = Get-Date $StartDateNoFormat -Format G 41 | 42 | # New search folder includes inbox messages 43 | New-OSCEXOSearchFolder -DisplayName "X.Inbox $EndDate" -Traversal Deep -WellKnownFolderName Inbox -StartDate $StartDate -EndDate $EndDate 44 | Start-Sleep -Seconds 5 # Sleep required: export fails before the search folder creation is complete 45 | # Export search folder results 46 | Get-OSCEXOSearchFolder "Operations.Response.Inbox $EndDate" | 47 | Export-OSCEXOEmailMessage -Path $emailDir -KeepSearchFolder 48 | 49 | # Copy emails with body content matches to valid email folder 50 | $bodyContentMatches = "example" 51 | (Get-ChildItem "$emailDir\*eml" | 52 | Select-String -SimpleMatch -Pattern $bodyContentMatches | 53 | Select-Object -ExpandProperty path -Unique) | 54 | ForEach-Object{Copy-Item -LiteralPath $_ $emailDir\validEmails} 55 | 56 | # Search for all email addresses using regex 57 | $input_path = "$emailDir\validEmails\*eml" 58 | $regex = ‘(\b[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}\b)|(^\..*)’ # Filter valid emails, include lines starting with periods 59 | select-string -SimpleMatch -Path $input_path -Pattern "MsoNormal" -context 1,3 | 60 | ForEach {$_ -Replace "@", ""} 61 | 62 | select-string -Path $input_path -Pattern $regex -AllMatches | 63 | # Exclude X addresses, postmaster, addresses with more than 8 digits, misc. 64 | Where-Object {$_ -NotLike "*X*" -and $_ -NotLike "*X*" | 65 | % { $_.Matches } | 66 | % { $_.Value } > $emailDir\validEmails\undeliverableEmailListDupes.txt 67 | 68 | # Remove duplicates 69 | $date = Get-Date -Format D # File timestamp for output 70 | Get-Content $emailDir\validEmails\undeliverableEmailListDupes.txt | 71 | Where-Object {$_ -notmatch "(.*\d.*){6}" -and $_ -notmatch '^[\.].*'} | # Remove lines starting with periods 72 | sort | 73 | select -unique > "$emailDir\validEmails\undeliverableEmailList_$date.txt" 74 | 75 | # Send email to x with attachment 76 | $un = "domain\x@x.com" 77 | $pw = '' 78 | $secstring = New-Object -TypeName System.Security.SecureString 79 | $pw.ToCharArray() | ForEach-Object {$secstring.AppendChar($_)} 80 | $credential = new-object -typename System.Management.Automation.PSCredential -argumentlist $un, $secstring 81 | Send-MailMessage -To X@X.com -cc X@X.com -Subject "Undeliverable Email List $StartDate - $EndDate" -Attachments "$emailDir\validEmails\undeliverableEmailList_$date.txt" -From "X@X.com" -SmtpServer smtp.office365.com -Port 587 -Credential $credential -UseSsl 82 | 83 | # Cleanup 84 | Get-Item $emailDir\*.eml | Remove-Item 85 | Get-Item $emailDir\validEmails\*.eml | Remove-Item 86 | Get-Item $emailDir\validEmails\undeliverableEmailListDupes.txt | Remove-Item 87 | -------------------------------------------------------------------------------- /Octopus_UpdateVariable.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | Script to update Octopus variable via Octopus API. 4 | Can be run manually or automated through an Orchestration tool/build system 5 | such as TFS/VSTS, Jenkins, TeamCity, etc. 6 | 7 | .EXAMPLE 8 | Example build step using TFS/VSTS to update BuildID Octopus variable with build ID system variable value 9 | 1. Add VSTS/TFS PowerShell script build step 10 | 2. Add APIKey as secret build definition variable 11 | 3. Pass as arguments in build step: BuildID $(Build.BuildID) $(APIKey) OctopusProjectName "http://MyOctopusInstance/octopus/" 12 | #> 13 | 14 | Param 15 | ( 16 | # Variable name to update 17 | [string(Mandatory=$true)]$VarName, 18 | 19 | # New value for variable 20 | [string(Mandatory=$true)]$Newvalue, 21 | 22 | # Octopus APIKey (should be encrypted build variable) 23 | [string(Mandatory=$true)]$APIKey, 24 | 25 | # Octopus Project Name 26 | [string(Mandatory=$true)]$ProjectName, 27 | 28 | # URL to your Octopus server instance 29 | [string(Mandatory=$true)]$OctopusURL 30 | ) 31 | 32 | Function UpdateOctopusVariable 33 | { 34 | Begin 35 | { 36 | <# 37 | Load required assemblies. They can be found in the Octopus Tentacle MSI: https://octopus.com/downloads 38 | These should be copied to your build server(s) if running via TFS/VSTS build. 39 | #> 40 | Add-Type -Path "$env:PROGRAMFILES\Octopus\Newtonsoft.Json.dll" 41 | Add-Type -Path "$env:PROGRAMFILES\Octopus\Octopus.Client.dll" 42 | 43 | # Connection data 44 | $endpoint = new-object Octopus.Client.OctopusServerEndpoint ($OctopusURL, $APIKey) 45 | $repository = new-object Octopus.Client.OctopusRepository $endpoint 46 | } 47 | 48 | Process 49 | { 50 | # Get project 51 | $project = $repository.Projects.FindByName($ProjectName) 52 | 53 | # Get project's variable set 54 | $variableset = $repository.VariableSets.Get($project.links.variables) 55 | 56 | # Get variable to update 57 | $variable = $variableset.Variables | ?{$_.name -eq $VarName} 58 | 59 | # Update variable 60 | $variable.Value = $newvalue 61 | } 62 | 63 | End 64 | { 65 | # Save variable set 66 | $repository.VariableSets.Modify($variableset) 67 | } 68 | } 69 | Try 70 | { 71 | write-host "Begin updating variable: $VarName value:$Newvalue " 72 | UpdateOctopusVariable 73 | write-host "Done updating variable: $VarName value:$Newvalue " 74 | 75 | } 76 | Catch 77 | { 78 | $result += $actionFailed 79 | $result += "`r`n Exception Type: $($_.Exception.GetType().FullName)" 80 | $result += "`r`n Exception Message: $($_.Exception.Message)" 81 | Exit 1 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerShell 2 | 3 | Misc scripts to interact with TFS/Azure DevOps REST API, AWS, deploy SSIS projects, set IIS properties, upload logs to FTP site, etc. 4 | -------------------------------------------------------------------------------- /SSIS_DeployISPAC.ps1: -------------------------------------------------------------------------------- 1 | # Script to deploy SSIS package (ISPAC) 2 | # ISPAC is built using devenv.exe 3 | # Use computer name to get IP then store in $server variable for connection string below 4 | $server = $env:computername 5 | $ips = [System.Net.Dns]::GetHostAddresses($server)[0].IPAddressToString; 6 | $server = $ips 7 | write-host "Server IP:" $server 8 | 9 | # Load the IntegrationServices Assembly 10 | $loadStatus = [Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Management.IntegrationServices,Culture=neutral") 11 | 12 | # Create a connection to the server 13 | $constr = "Data Source=$server;Initial Catalog=master;Integrated Security=SSPI;" 14 | $con = New-Object System.Data.SqlClient.SqlConnection $constr 15 | 16 | # Store the IntegrationServices Assembly namespace to avoid typing it every time 17 | $ISNamespace = "Microsoft.SqlServer.Management.IntegrationServices" 18 | Write-Host "Connecting to server ..." 19 | 20 | # Create the Integration Services object 21 | $ssis = New-Object $ISNamespace".IntegrationServices" $con 22 | 23 | # Check if catalog exists 24 | if ($ssis.Catalogs.Count -eq 0) 25 | { 26 | Write-Error "SSISDB doesn't exist" 27 | throw "SSISDB doesn't exist" 28 | } 29 | 30 | # Set catalog to SSISDB 31 | $cat = $ssis.Catalogs["SSISDB"] 32 | 33 | # If $ProjectName folder in SSISDB doesn't exist, create it 34 | $folderName = "$ProjectName" 35 | if ($cat.Folders[$folderName] -eq $null) 36 | { 37 | Write-Host "Creating new folder" $folderName 38 | $newfolder = New-Object $ISNamespace".CatalogFolder" ($cat, $folderName, "Description") 39 | $newfolder.Create() 40 | } 41 | 42 | # Set folder to catalog folder ($ProjectName) 43 | $folder = $cat.Folders[$folderName] 44 | 45 | # Set dir of ISPAC file 46 | $localToLocalETLFullPath = "$PSScriptRoot\bin\$ProjectName.$DatabaseName.ispac" 47 | 48 | # Read the project file, and deploy it to the folder 49 | Write-Host "Deploying SSIS project ..." 50 | [byte[]] $projectFile = [System.IO.File]::ReadAllBytes($localToLocalETLFullPath) 51 | $folder.DeployProject("$ProjectName.$DatabaseName.ETL", $projectFile) 52 | -------------------------------------------------------------------------------- /TFS_AggregateCodeCoverageResults.ps1: -------------------------------------------------------------------------------- 1 | # Script to aggregate TFS 2015 build code coverage results for specified build definition 2 | # SSRS reports do not work with new build system (Non XAML) to provide this info as of Update 2 3 | 4 | # Encrypted password passed via build definition variable 5 | param( 6 | [string]$passwd 7 | ) 8 | 9 | Function AggregateCodeCoverageResults 10 | { 11 | $definitionId = "" 12 | $user = "" 13 | $secpasswd = ConvertTo-SecureString $passwd -AsPlainText -Force 14 | $credential = New-Object System.Management.Automation.PSCredential($user, $secpasswd) 15 | 16 | # Use build api to get build IDs and build dates. 17 | [uri] $buildUri = $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI + $env:SYSTEM_TEAMPROJECT + "/_apis/build/builds?definitions=" + $definitionId +"&statusFilter=completed&reasonfilter=scheduled&api-version=2.0" 18 | $getBuilds = Invoke-RestMethod -Uri $buildUri -Method Get -Credential $credential 19 | 20 | # Create object from results containing build date and build number 21 | $buildList = $getBuilds | 22 | select @{Name="BuildDate"; Expression={$_.value.finishtime}},@{Name="BuildNumber"; Expression={$_.value.buildNumber}} 23 | 24 | # Build number will be used in code coverage Uri 25 | $buildNumbers = $buildList.BuildNumber 26 | 27 | # Build dates will be added to results array at line 70 28 | $buildDates = $buildList.BuildDate 29 | 30 | # Trim anything past xxxx-xx-xx 31 | $buildDatesArray = $buildDates.Substring(0,10) 32 | 33 | # Initialize array that will contain results 34 | $coverageResultsArray = @() 35 | 36 | # Loop through every build ID to get code coverage results 37 | ForEach($buildNumber in $buildNumbers) 38 | { 39 | # Use code coverege api to get results of build 40 | [uri] $codeCoverageUri = $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI + $env:SYSTEM_TEAMPROJECT + "/_apis/test/codecoverage?api-version=2.0-preview.1&buildid=" + $buildNumber +"&flags=7" 41 | $coverageResults = Invoke-RestMethod -Uri $codeCoverageUri -Method Get -Credential $credential 42 | 43 | # Get total blocks covered and blocks NOT covered, sum them up to get toal blocks, then divide blocks covered by total blocks 44 | $blocksCovered = $coverageResults.value.modules.statistics.blocksCovered 45 | $blocksNotCovered = $coverageResults.value.modules.statistics.blocksNotCovered 46 | $blocksCoveredSum = $blockscovered | measure-object -sum 47 | $blocksNotCoveredSum = $blocksNotCovered | measure-object -sum 48 | $totalBlocks = $blocksCoveredSum.sum+$blocksNotCoveredSum.sum 49 | $dividedBlocks = $blocksCoveredSum.sum/$totalBlocks 50 | 51 | # move decimal over 2, add percent symbol 52 | $coveragePercent = "{00:P2}" -f $dividedBlocks 53 | 54 | # Failed builds return NaN for coverage result 55 | if($coveragePercent -eq "NaN") 56 | { 57 | # Add "build failed" to results array 58 | $coverageResultsArray += "Build Failed" 59 | } 60 | else 61 | { 62 | # Add coverage percent to results array 63 | $coverageResultsArray += $coveragePercent 64 | } 65 | 66 | } 67 | 68 | # Loop through and add build dates to coverage results output to CSV 69 | $(for ($i=0; $i -le $coverageResultsArray.Count;$i++) 70 | { 71 | 'Build Date: {0} Coverage: {1}' -f $buildDatesArray[$i],$coverageResultsArray[$i] 72 | }) | set-content C:\codecoverage.csv 73 | 74 | } 75 | Try 76 | { 77 | AggregateCodeCoverageResults 78 | } 79 | Catch 80 | { 81 | Write-Host "Exception Type: $($_.Exception.GetType().FullName)" 82 | Write-Host "Exception Message: $($_.Exception.Message)" 83 | } 84 | -------------------------------------------------------------------------------- /TFS_CreateBranchPolicies.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Uses the TFS REST API to apply branch policies to specified branch 4 | .DESCRIPTION 5 | This script uses the TFS REST API for configurations to set branch policies for: 6 | -approver group 7 | -Minimum approvers (2) 8 | -Required build 9 | -Work Item required 10 | .NOTES 11 | -Branch is set via build definition variable at queue time 12 | #> 13 | param( 14 | [string]$global:branch, # Branch to set policies on passed via build definition when queueing build 15 | [string]$global:user, # Service account user name passed via build definition 16 | [string]$global:passwd # Encrypted password passed via build definition 17 | ) 18 | 19 | # Use TFS REST API for configurations: https://www.visualstudio.com/en-us/docs/integrate/api/policy/configurations#create-a-policy-configuration 20 | [uri] $global:PolicyUri = $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI + $env:SYSTEM_TEAMPROJECT + "/_apis/policy/configurations?api-version=2.0-preview" 21 | write-host $PolicyUri 22 | 23 | # Create secure credential 24 | $global:secpasswd = ConvertTo-SecureString $passwd -AsPlainText -Force 25 | $global:credential = New-Object System.Management.Automation.PSCredential($user, $secpasswd) 26 | 27 | Function ApplyApproverPolicy 28 | { 29 | 30 | # JSON for setting appovers 31 | $JSONBody= @" 32 | { 33 | "isEnabled": true, 34 | "isBlocking": true, 35 | "type": { 36 | "id": "" 37 | }, 38 | "settings": { 39 | "requiredReviewerIds": [ 40 | "" 41 | ], 42 | "filenamePatterns": [ 43 | "/ExamplePath" 44 | ], 45 | "addedFilesOnly": false, 46 | "scope": [ 47 | { 48 | "repositoryId": null, 49 | "refName": "refs/heads/$Branch", 50 | "matchKind": "exact" 51 | }, 52 | { 53 | "repositoryId": null, 54 | "refName": "refs/heads/$Branch/", 55 | "matchKind": "prefix" 56 | } 57 | ] 58 | } 59 | } 60 | "@ 61 | 62 | # Use URI and JSON above to apply approver policy to specified branch 63 | Invoke-RestMethod -Uri $PolicyUri -Method Post -ContentType application/json -Body $JSONBody -Credential $credential 64 | 65 | } 66 | 67 | Function ApplyMinimumApproverPolicy 68 | { 69 | 70 | # JSON for setting minimum approval count policy 71 | $JSONBody= @" 72 | { 73 | "isEnabled": true, 74 | "isBlocking": true, 75 | "type": { 76 | "id": "" 77 | }, 78 | "settings": { 79 | "minimumApproverCount": 2, 80 | "scope": [ 81 | { 82 | "repositoryId": null, 83 | "refName": "refs/heads/$branch", 84 | "matchKind": "exact" 85 | } 86 | ] 87 | } 88 | } 89 | "@ 90 | 91 | # Use URI and JSON above to apply minimum approver policy to specified branch 92 | Invoke-RestMethod -Uri $PolicyUri -Method Post -ContentType application/json -Body $JSONBody -Credential $credential 93 | 94 | } 95 | 96 | Function ApplyBuildPolicy 97 | { 98 | 99 | # JSON for setting required build policy 100 | $JSONBody= @" 101 | { 102 | "isEnabled": true, 103 | "isBlocking": true, 104 | "type": { 105 | "id": "" 106 | }, 107 | "settings": { 108 | "buildDefinitionId": buildID, 109 | "scope": [ 110 | { 111 | "repositoryId": null, 112 | "refName": "refs/heads/$branch", 113 | "matchKind": "exact" 114 | } 115 | ] 116 | } 117 | } 118 | "@ 119 | 120 | # Use URI and JSON above to apply build policy to specified branch 121 | Invoke-RestMethod -Uri $PolicyUri -Method Post -ContentType application/json -Body $JSONBody -Credential $credential 122 | 123 | } 124 | 125 | Function ApplyWorkItemPolicy 126 | { 127 | 128 | # JSON for setting work item required policy 129 | $JSONBody= @" 130 | { 131 | "isEnabled": true, 132 | "isBlocking": true, 133 | "type": { 134 | "id": "" 135 | }, 136 | "settings": { 137 | "scope": [ 138 | { 139 | "repositoryId": null, 140 | "refName": "refs/heads/$branch", 141 | "matchKind": "exact" 142 | } 143 | ] 144 | } 145 | } 146 | "@ 147 | 148 | # Use URI and JSON above to apply work item required to specified branch 149 | Invoke-RestMethod -Uri $PolicyUri -Method Post -ContentType application/json -Body $JSONBody -Credential $credential 150 | 151 | } 152 | 153 | Try 154 | { 155 | ApplyApproverPolicy 156 | write-host "Approver Policy set on branch: $branch" 157 | ApplyMinimumApproverPolicy 158 | write-host "Minimum Approver Policy set on branch: $branch" 159 | ApplyBuildPolicy 160 | write-host "Build Policy set on branch: $branch" 161 | ApplyWorkItemPolicy 162 | write-host "Work Item required Policy set on branch: $branch" 163 | 164 | } 165 | Catch 166 | { 167 | $result = $_.Exception.Response.GetResponseStream() 168 | $reader = New-Object System.IO.StreamReader($result) 169 | $reader.BaseStream.Position = 0 170 | $reader.DiscardBufferedData() 171 | $responseBody = $reader.ReadToEnd(); 172 | write-host $responseBody 173 | } 174 | Finally 175 | { 176 | # Clear global variable to cleanup 177 | Clear-Variable user -Scope Global 178 | Clear-Variable pwd -Scope Global 179 | Clear-Variable branch -Scope Global 180 | Clear-Variable PolicyUri -Scope Global 181 | } 182 | -------------------------------------------------------------------------------- /TFS_GetCommitAssociatedToBuild.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Script gets commit from last successful build. 4 | .Notes 5 | TFS REST API documentation: https://www.visualstudio.com/integrate/api/build/builds 6 | #> 7 | 8 | function GetCommitAssociatedToBuild{ 9 | Param( 10 | [string]$DefinitionID = # Build Definition ID (found on variables tab) 11 | ) 12 | 13 | # Get last completed build 14 | [uri] $BuildUri = $env:System_TeamFoundationCollectionUri + $env:System_TeamProject + "/_apis/build/builds?definitions=$DefinitionID&api-version=2.0&statusFilter=completed&`$top=1" 15 | $BuildInfo = Invoke-RestMethod -Method Get -Uri $BuildUri -ContentType 'application/json' -UseDefaultCredentials 16 | 17 | # Fail script if no build is found 18 | If($BuildInfo.count -ne 1){ 19 | write-output "No build found" 20 | exit -1 21 | } 22 | # Get source version from build info 23 | $SourceVersion = $BuildInfo.value.sourceversion 24 | write-host "SourceVersion = $SourceVersion" 25 | 26 | # Write variable to be used in subsequent build steps 27 | Write-Host ("##vso[task.setvariable variable=SourceVersion;]$SourceVersion") 28 | 29 | } 30 | 31 | Try{ 32 | GetCommitAssociatedToBuild 33 | } 34 | Catch{ 35 | Write-Host "Exception Type: $($_.Exception.GetType().FullName)" 36 | Write-Host "Exception Message: $($_.Exception.Message)" 37 | exit 1 38 | } 39 | -------------------------------------------------------------------------------- /TFS_PauseBuildsPerEnvironment.ps1: -------------------------------------------------------------------------------- 1 |  2 | <# 3 | .DESCRIPTION 4 | Gets all build definitions for specified environment name, 5 | enables or disables the CI triggers in order to prevent builds from 6 | being queued during demo's. 7 | 8 | .PARAMETER Action 9 | Enable or Disable build definition CI triggers 10 | 11 | .PARAMETER EnvironmentName 12 | Name of environment to disable/enable builds for 13 | 14 | .PARAMETER PAT 15 | Personal Access token. It's recommended to use a service account and pass via encrypted build definition variable. 16 | 17 | .Notes 18 | -These parameters are outside of functions in order to be passed by TFS build definition variables 19 | -Triggers aren't actually "disabled", just flipped Include/Exclude on path filter 20 | #> 21 | [CmdletBinding()] 22 | Param( 23 | [Parameter(Position=0,Mandatory)] 24 | [ValidateSet("Enable","Disable")] 25 | [string]$script:Action, 26 | [Parameter(Position=1,Mandatory)] 27 | [ValidateSet("Environment1","Environment2","Environment3")] # Case is ignored by default 28 | [string]$script:EnvironmentName, 29 | [Parameter(Position=2,Mandatory)] 30 | [ValidateNotNullOrEmpty()] 31 | [string]$script:PAT 32 | ) 33 | 34 | # Base64-encodes the Personal Access Token (PAT) appropriately 35 | # This is required to pass PAT through HTTP header 36 | $script:User = "" # Not needed when using PAT, can be set to anything 37 | $script:Base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $User,$PAT))) 38 | 39 | Function Get-BuildDefinitionsByEnvironment { 40 | <# 41 | .OUTPUT 42 | DefinitionIDs 43 | #> 44 | [cmdletbinding()] 45 | Param( 46 | ) 47 | Try{ 48 | # https://docs.microsoft.com/en-us/rest/api/vsts/build/definitions/get 49 | [uri] $script:Uri = $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI + $env:SYSTEM_TEAMPROJECT + "/_apis/build/definitions?type=build&name=*$EnvironmentName*" 50 | 51 | # Invoke the REST call and capture the response 52 | $Response = Invoke-RestMethod -Uri $Uri ` 53 | -Method Get ` 54 | -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} ` 55 | 56 | "Get-BuildDefinitionsByEnvironment returned response: $Response" 57 | "Found build definitions:" 58 | $Response.value.name 59 | $script:DefinitionIDs = $Response.value.id # Scope variable to "script" to be used in other functions 60 | } 61 | Catch{ 62 | $result = $_.Exception.Response.GetResponseStream() 63 | $reader = New-Object System.IO.StreamReader($result) 64 | $reader.BaseStream.Position = 0 65 | $reader.DiscardBufferedData() 66 | $responseBody = $reader.ReadToEnd(); 67 | $responseBody 68 | Exit 1 69 | } 70 | } 71 | 72 | Function Update-BuildDefinitionTriggerPath { 73 | [cmdletbinding()] 74 | Param( 75 | ) 76 | Try{ 77 | # https://docs.microsoft.com/en-us/rest/api/vsts/build/definitions/update 78 | [uri] $script:Uri = $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI + $env:SYSTEM_TEAMPROJECT + "/_apis/build/definitions/$DefinitionID" + "?api-version=3.0" 79 | 80 | # Get definition to update, use response for json body to update definition 81 | $Definition = Invoke-RestMethod -Uri $Uri ` 82 | -Method Get ` 83 | -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 84 | 85 | If($Definition.triggers.pathFilters){ 86 | $DefinitionTriggerPaths = $Definition.triggers.pathFilters 87 | } 88 | Else{ 89 | "No path filter, moving on to next definition" 90 | continue # move to next definition in foreach loop 91 | } 92 | # Convert response to JSON to be used in Put below 93 | $Definition = $Definition | ConvertTo-Json -Depth 100 94 | 95 | ForEach($Path in $DefinitionTriggerPaths){ 96 | # Replace first character in path filter: + include, - exclude 97 | # Example: +$/ED/SCM 98 | Switch($Action.ToLower()){ 99 | "enable" { 100 | $PathAfter = $Path.Replace("-$/","+$/") 101 | } 102 | "disable" { 103 | $PathAfter = $Path.Replace("+$/","-$/") 104 | } 105 | default { 106 | Write-Error "Not a valid action. Actions available: Enable, Disable" 107 | } 108 | } 109 | 110 | # replace old trigger path with new 111 | $Definition = $Definition.Replace("$Path","$PathAfter") 112 | 113 | } 114 | 115 | # Use updated response to update definition 116 | $UpdatedDefinition = Invoke-RestMethod -Uri $Uri ` 117 | -Method Put ` 118 | -ContentType application/json ` 119 | -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} ` 120 | -Body $Definition 121 | 122 | "Trigger updated:" 123 | $UpdatedDefinition.triggers 124 | } 125 | Catch{ 126 | $result = $_.Exception.Response.GetResponseStream() 127 | $reader = New-Object System.IO.StreamReader($result) 128 | $reader.BaseStream.Position = 0 129 | $reader.DiscardBufferedData() 130 | $responseBody = $reader.ReadToEnd(); 131 | $responseBody 132 | Exit 1 133 | } 134 | } 135 | 136 | Try 137 | { 138 | "Getting build definitions for environment: $EnvironmentName..." 139 | Get-BuildDefinitionsByEnvironment 140 | 141 | "$Action build definition triggers..." 142 | ForEach($DefinitionID in $DefinitionIDs){ 143 | "$Action trigger for $DefinitionID" 144 | Update-BuildDefinitionTriggerPath 145 | } 146 | } 147 | Catch 148 | { 149 | Write-Error $_ 150 | Exit 1 151 | } -------------------------------------------------------------------------------- /TFS_UpdateWorkItemsWithBuildLink.ps1: -------------------------------------------------------------------------------- 1 | # Adds build link to associate work items 2 | # Runs as the last step in build definitions 3 | 4 | # Encrypted password passed via build definition 5 | param( 6 | [string]$passwd 7 | ) 8 | 9 | Function UpdateWorkItemsWithBuildLink 10 | { 11 | $user = "" 12 | $secpasswd = ConvertTo-SecureString $passwd -AsPlainText -Force 13 | $credential = New-Object System.Management.Automation.PSCredential($user, $secpasswd) 14 | 15 | # Uri to get associated work items: https://www.visualstudio.com/en-us/integrate/api/build/builds#GetbuilddetailsWorkitems 16 | [uri] $BuildWorkItemsUri = $tfsUrl + $env:SYSTEM_TEAMPROJECT + "/_apis/build/builds/" + $env:BUILD_BUILDID +"/workitems?api-version=2.0" 17 | write-host $BuildWorkItemsUri 18 | 19 | try 20 | { 21 | # Get build details: Workitems 22 | $results = Invoke-RestMethod -Uri $BuildWorkItemsUri -Method Get -Credential $credential 23 | write-host $results 24 | } 25 | catch 26 | { 27 | # Catch 404 and move on 28 | $_.Exception.Response.StatusCode.Value__ 29 | } 30 | 31 | $WorkitemIDs = $results.value.id 32 | 33 | # List array of associated work items 34 | write-host "Associated Work Item IDs:"$WorkitemIDs 35 | 36 | # Loop through each work item and update with link to build 37 | ForEach($WorkItemID in $WorkitemIDs) 38 | { 39 | # Uri to add hyperlink to work items: https://www.visualstudio.com/integrate/api/wit/work-items#UpdateworkitemsAddalink 40 | [uri] $WorkItemLinkUri = $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI + "_apis/wit/workitems/" + $WorkItemID +"?api-version=1.0" 41 | write-host $WorkItemLinkUri 42 | 43 | $JSONBody= @" 44 | [{ 45 | "op": "add", 46 | "path": "/relations/-", 47 | "value": { 48 | "rel": "Hyperlink", 49 | "url": " $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI$env:SYSTEM_TEAMPROJECT/_build#buildId=$env:BUILD_BUILDID&_a=summary" 50 | } 51 | }] 52 | "@ 53 | # Add a link to each work item with build link 54 | Invoke-RestMethod -Uri $WorkItemLinkUri -Method Patch -ContentType application/json-patch+json -Body $JSONBody -Credential $credential 55 | } 56 | } 57 | 58 | Try 59 | { 60 | UpdateWorkItemsWithBuildLink 61 | } 62 | Catch 63 | { 64 | Write-Host "Exception Type: $($_.Exception.GetType().FullName)" 65 | Write-Host "Exception Message: $($_.Exception.Message)" 66 | } 67 | -------------------------------------------------------------------------------- /Update-AzDevOpsBuildAgentPools-.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Description 3 | Script used to bulk update Azure Pipeline build definition's agent pools to Default 4 | #> 5 | 6 | $PAT = "" 7 | $AzureDevOpsOrgURL = "" # https://dev.azure.com/{organization} 8 | 9 | # Base64-encodes the Personal Access Token (PAT) appropriately 10 | # This is required to pass PAT through HTTP header 11 | $script:User = "" # Not needed when using PAT, can be set to anything 12 | $script:Base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $User,$PAT))) 13 | 14 | # Get all projects in organization 15 | [uri] $script:GetProjectsUri = "$AzureDevOpsOrgURL/_apis/projects`?api-version=5.1" 16 | $GetProjectsResponse = Invoke-RestMethod -Uri $GetProjectsUri -Method Get -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 17 | 18 | # Get name of projects from response object 19 | $Projects = $GetProjectsResponse.value.name 20 | 21 | # Loop through each project and update it's build and Build definitions 22 | ForEach($Project in $Projects){ 23 | "Updating definitions in Project: $Project" 24 | 25 | # Get Queue ID for project 26 | [uri] $script:GetQueueURI = "$AzureDevOpsOrgURL/$Project/_apis/distributedtask/queues" 27 | $GetQueueResponse = Invoke-RestMethod -Uri $GetQueueURI -Method GET -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 28 | 29 | $QueueID = $GetQueueResponse.value | Where-Object {$_.name -eq "Default"} 30 | $QueueID = $QueueID.id 31 | 32 | If($QueueID){ 33 | 34 | # Get all Build definitions in project 35 | [uri] $script:GetBuildDefinitionsUri = "$AzureDevOpsOrgURL/$Project/_apis/build/definitions" 36 | 37 | Try{ 38 | $GetBuildDefinitionsResponse = Invoke-RestMethod -Uri $GetBuildDefinitionsUri -Method Get -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 39 | } 40 | Catch{ 41 | "Did not find Builds in project: $Project" 42 | Continue 43 | } 44 | # Get definition ID's from response object 45 | $DefinitionIDs = $GetBuildDefinitionsResponse.value.id 46 | 47 | # Loop through and request each definition 48 | ForEach($DefinitionID in $DefinitionIDs){ 49 | 50 | [uri] $script:GetDefinitionURI = "$AzureDevOpsOrgURL/$Project/_apis/build/Definitions/$DefinitionID" 51 | $GetDefinitionResponse = Invoke-RestMethod -Uri $GetDefinitionURI -Method GET -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 52 | 53 | # Set Agent pool and all properties to default 54 | $GetDefinitionResponse.queue | Add-Member -MemberType NoteProperty -Name 'id' -Value $QueueID -Force 55 | $GetDefinitionResponse.queue | Add-Member -MemberType NoteProperty -Name 'name' -Value "Default" -Force 56 | $GetDefinitionResponse.queue | Add-Member -MemberType NoteProperty -Name 'url' -Value "$AzureDevOpsOrgURL/_apis/build/Queues/$QueueID" -Force 57 | If($GetDefinitionResponse.process.phases.target.queue){$GetDefinitionResponse.process.phases.target.queue | Add-Member -MemberType NoteProperty -Name 'id' -Value $QueueID -Force} 58 | If($GetDefinitionResponse.process.phases.target.queue){$GetDefinitionResponse.process.phases.target.queue | Add-Member -MemberType NoteProperty -Name 'url' -Value "$AzureDevOpsOrgURL/_apis/build/Queues/$QueueID" -Force} 59 | $GetDefinitionResponse.queue | Add-Member -MemberType NoteProperty -Name 'pool' -Value "" -Force 60 | $GetDefinitionResponse.queue.pool | Add-Member -MemberType NoteProperty -Name 'id' -Value 1 -Force 61 | $GetDefinitionResponse.queue.pool | Add-Member -MemberType NoteProperty -Name 'name' -Value "Default" -Force 62 | If($GetDefinitionResponse.queue.pool.isHosted){$GetDefinitionResponse.queue.pool.isHosted = $false} 63 | If($GetDefinitionResponse.queue.pool.isHosted){$GetDefinitionResponse.process.target.agentSpecification = ""} 64 | 65 | # Convert response to JSON to be used in Put below 66 | $Definition = $GetDefinitionResponse | ConvertTo-Json -Depth 100 67 | 68 | $Definition = $Definition -replace "build/Queues/\d+", "build/Queues/$QueueID" 69 | 70 | # Use updated response to update definition 71 | # Note: Build Definition ID is not needed in URI for PUT 72 | $script:UpdateDefinitionURI = "$AzureDevOpsOrgURL/$Project/_apis/Build/definitions/$DefinitionID`?api-version=5.1" 73 | Invoke-RestMethod -Uri $UpdateDefinitionURI -Method Put -ContentType application/json -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} -Body $Definition 74 | } 75 | } 76 | Else{ 77 | "Did not find Default queue in project: $Project" 78 | } 79 | } 80 | 81 | 82 | -------------------------------------------------------------------------------- /Update-AzDevOpsReleaseAgentPools.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Description 3 | Script used to bulk update Azure Pipeline release definition's and set agent pools to Default 4 | #> 5 | 6 | $PAT = "6up2qi26kkrcetyqruudggvbqwj75f7t4jw2bcjbkcno3yiisceq" 7 | $AzureDevOpsOrgURL = "https://dev.azure.com/MSFT-MarcusFelling" # https://dev.azure.com/{organization} 8 | $AzureDevOpsVSRMURL = $AzureDevOpsOrgURL -replace "dev.azure.com", "vsrm.dev.azure.com" # Add vsrm (visual studio release management) to URI 9 | 10 | # Base64-encodes the Personal Access Token (PAT) appropriately 11 | # This is required to pass PAT through HTTP header 12 | $script:User = "" # Not needed when using PAT, can be set to anything 13 | $script:Base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $User,$PAT))) 14 | 15 | # Get all projects in organization 16 | [uri] $script:GetProjectsUri = "$AzureDevOpsOrgURL/_apis/projects`?api-version=5.1" 17 | $GetProjectsResponse = Invoke-RestMethod -Uri $GetProjectsUri -Method Get -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 18 | 19 | # Get name of projects from response object 20 | $Projects = $GetProjectsResponse.value.name 21 | 22 | # Loop through each project and update it's build and release definitions 23 | ForEach($Project in $Projects){ 24 | "Updating definitions in Project: $Project" 25 | 26 | # Get Queue ID for project 27 | [uri] $script:GetQueueURI = "$AzureDevOpsOrgURL/$Project/_apis/distributedtask/queues" 28 | $GetQueueResponse = Invoke-RestMethod -Uri $GetQueueURI -Method GET -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 29 | 30 | $QueueID = $GetQueueResponse.value | Where-Object {$_.name -eq "Default"} 31 | $QueueID = $QueueID.id 32 | 33 | If($QueueID){ 34 | 35 | # Get all release definitions in project 36 | [uri] $script:GetReleaseDefinitionsUri = "$AzureDevOpsVSRMURL/$Project/_apis/Release/definitions" 37 | 38 | Try{ 39 | $GetReleaseDefinitionsResponse = Invoke-RestMethod -Uri $GetReleaseDefinitionsUri -Method Get -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 40 | } 41 | Catch{ 42 | "Did not find releases in project: $Project" 43 | Continue 44 | } 45 | # Get definition ID's from response object 46 | $DefinitionIDs = $GetReleaseDefinitionsResponse.value.id 47 | 48 | # Loop through and request each definition 49 | ForEach($DefinitionID in $DefinitionIDs){ 50 | 51 | [uri] $script:GetDefinitionURI = "$AzureDevOpsVSRMURL/$Project/_apis/release/definitions/$DefinitionID`?`$expand=Environments" 52 | $GetDefinitionResponse = Invoke-RestMethod -Uri $GetDefinitionURI -Method GET -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 53 | 54 | # Convert response to JSON to be used in Put below 55 | $Definition = $GetDefinitionResponse | ConvertTo-Json -Depth 100 56 | 57 | # Set Agent Pool to Default if 0 58 | $Definition = $Definition -replace "`"queueId`": \d+", "`"queueId`": $QueueID" 59 | 60 | # Use updated response to update definition 61 | # Note: Release Definition ID is not needed in URI for PUT 62 | $script:UpdateDefinitionURI = "$AzureDevOpsVSRMURL/$Project/_apis/release/definitions`?api-version=5.0" 63 | Invoke-RestMethod -Uri $UpdateDefinitionURI -Method Put -ContentType application/json -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} -Body $Definition 64 | } 65 | } 66 | Else{ 67 | "Did not find Default queue in project: $Project" 68 | } 69 | } 70 | 71 | 72 | -------------------------------------------------------------------------------- /Update-ReleaseDefinitionsWindowsMachineFileCopyTask.ps1: -------------------------------------------------------------------------------- 1 | $TFSBaseURL = "" 2 | $Project = "" 3 | 4 | # Base64-encodes the Personal Access Token (PAT) appropriately 5 | # This is required to pass PAT through HTTP header 6 | $script:User = "" # Not needed when using PAT, can be set to anything 7 | $script:Base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $User,$PAT))) 8 | 9 | # Get list of all release definitions 10 | [uri] $script:GetDefinitionsURI = "$TFSBaseURL/$Project/_apis/release/definitions" 11 | 12 | # Invoke the REST call and capture the response 13 | $GetDefinitionsUriResponse = Invoke-RestMethod -Uri $GetDefinitionsURI -Method Get -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 14 | $DefinitionIDs = $GetDefinitionsUriResponse.value.id 15 | 16 | ForEach($DefinitionID in $DefinitionIDs){ 17 | [uri] $script:GetDefinitionsURI = "$TFSBaseURL/$Project/_apis/release/definitions/$DefinitionID" 18 | $GetDefinitionsUriResponse = Invoke-RestMethod -Uri $GetDefinitionsURI -Method GET -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} 19 | $FileCopyTasks = $GetDefinitionsUriResponse.environments.deployPhases.workflowTasks | Where-Object refName -like "WindowsMachineFileCopy*" 20 | ForEach($Task in $FileCopyTasks){ 21 | # Update task to v2 22 | $Task.version = '2.*' 23 | 24 | # Bug #1 MachineName was lost in upgrade, add it back using EnvironmentName 25 | $Task.inputs.MachineNames = $Task.inputs.EnvironmentName 26 | } 27 | } 28 | 29 | # Convert response to JSON to be used in Put below 30 | $Definition = $GetDefinitionsUriResponse | ConvertTo-Json -Depth 100 31 | 32 | # Use updated response to update definition 33 | $script:UpdateDefinitionURI = "$TFSBaseURL/$Project/_apis/release/definitions?api-version=5.0" 34 | $UpdatedDefinition = Invoke-RestMethod -Uri $UpdateDefinitionURI -Method Put -ContentType application/json -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} -Body $Definition 35 | -------------------------------------------------------------------------------- /VSTS_CreateReleasePullRequest.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Uses the VSTS REST API to create pull request 4 | 5 | .DESCRIPTION 6 | This script uses the VSTS REST API to create a Pull Request in the specified 7 | repository, source and target branches. Intended to run via VSTS Build using a build step for each repository. 8 | https://www.visualstudio.com/en-us/docs/integrate/api/git/pull-requests/pull-requests 9 | 10 | .NOTES 11 | Existing branch policies are automatically applied. 12 | 13 | .PARAMETER Repository 14 | Repository to create PR in 15 | 16 | .PARAMETER SourceRefName 17 | The name of the source branch without ref. 18 | 19 | .PARAMETER TargetRefName 20 | The name of the target branch without ref. 21 | 22 | .PARAMETER APIVersion 23 | API versions are in the format {major}.{minor}[-{stage}[.{resource-version}]] - For example, 1.0, 1.1, 1.2-preview, 2.0. 24 | 25 | .PARAMETER ReviewerGUID 26 | ID(s) of the initial reviewer(s). Not mandadory. 27 | Can be found in existing PR by using GET https://{instance}/DefaultCollection/{project}/_apis/git/repositories/{repository}/pullRequests/{pullrequestid}?api-version=3.0 28 | 29 | .PARAMETER PAT 30 | Personal Access token. It's recommended to use a service account and pass via encrypted build definition variable. 31 | #> 32 | [CmdletBinding()] 33 | Param( 34 | [Parameter(Position=0,Mandatory=$true,ValueFromPipeline)] 35 | [ValidateNotNullOrEmpty()] 36 | [string]$script:Repository, 37 | [Parameter(Position=1,Mandatory=$true,ValueFromPipeline)] 38 | [ValidateNotNullOrEmpty()] 39 | [string]$script:SourceRefName, 40 | [Parameter(Position=2,Mandatory=$true,ValueFromPipeline)] 41 | [ValidateNotNullOrEmpty()] 42 | [string]$script:TargetRefName, 43 | [Parameter(Position=3,Mandatory=$true,ValueFromPipeline)] 44 | [ValidateNotNullOrEmpty()] 45 | [string]$script:APIVersion, 46 | [Parameter(Position=4,Mandatory=$true,ValueFromPipeline)] 47 | [ValidateNotNullOrEmpty()] 48 | [int]$script:ReviewerGUID, 49 | [Parameter(Position=5,Mandatory=$false,ValueFromPipeline)] 50 | [ValidateNotNullOrEmpty()] 51 | [string]$script:PAT 52 | ) 53 | 54 | Function CreatePullRequest 55 | { 56 | # Contruct Uri for Pull Requests: https://{instance}/DefaultCollection/{project}/_apis/git/repositories/{repository}/pullRequests?api-version={version} 57 | # Note: /DefaultCollection/ is required for all VSTS accounts 58 | # Environment variables are populated when running via VSTS build 59 | [uri] $PRUri = $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI + "/DefaultCollection/" + $env:SYSTEM_TEAMPROJECT + "/_apis/git/repositories/$Repository/pullRequests?api-version=$APIVersion" 60 | 61 | # Base64-encodes the Personal Access Token (PAT) appropriately 62 | # This is required to pass PAT through HTTP header in Invoke-RestMethod bellow 63 | $User = "" # Not needed when using PAT, can be set to anything 64 | $Base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $User,$PAT))) 65 | 66 | # Prepend refs/heads/ to branches so shortened version can be used in title 67 | $Ref = "refs/heads/" 68 | $SourceBranch = "$Ref" + "$SourceRefName" 69 | $TargetBranch = "$Ref" + "$TargetRefName" 70 | 71 | # JSON for creating PR with Reviewer specified 72 | If($ReviewerGUID){ 73 | $JSONBody= @" 74 | { 75 | "sourceRefName": "$SourceBranch", 76 | "targetRefName": "$TargetBranch", 77 | "title": "Merge $sourceRefName to $targetRefName", 78 | "description": "PR Created automajically via REST API ", 79 | "reviewers": [ 80 | { 81 | "id": { $ReviewerGUID } 82 | } 83 | ] 84 | } 85 | "@ 86 | } 87 | Else{ 88 | # JSON for creating PR without Reviewer specified 89 | $JSONBody= @" 90 | { 91 | "sourceRefName": "$SourceBranch", 92 | "targetRefName": "$TargetBranch", 93 | "title": "Merge $sourceRefName to $targetRefName", 94 | "description": "PR Created automajically via REST API ", 95 | } 96 | "@ 97 | } 98 | 99 | # Use URI and JSON above to invoke the REST call and capture the response. 100 | $Response = Invoke-RestMethod -Uri $PRUri ` 101 | -Method Post ` 102 | -ContentType "application/json" ` 103 | -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} ` 104 | -Body $JSONBody 105 | 106 | # Get new PR info from response 107 | $script:NewPRID = $Response.pullRequestId 108 | $script:NewPRURL = $Response.url 109 | } 110 | 111 | Try 112 | { 113 | "Creating PR in $Repository repository: Source branch $SourceRefName Target Branch: $TargetRefName" 114 | CreatePullRequest 115 | "Created PR $NewPRID`: $NewPRURL" 116 | } 117 | Catch 118 | { 119 | $result = $_.Exception.Response.GetResponseStream() 120 | $reader = New-Object System.IO.StreamReader($result) 121 | $reader.BaseStream.Position = 0 122 | $reader.DiscardBufferedData() 123 | $responseBody = $reader.ReadToEnd(); 124 | $responseBody 125 | Exit 1 # Fail build if errors 126 | } 127 | --------------------------------------------------------------------------------