Tips for Writing PowerShell Scripts to Use in Build and Release Tasks


Premier Developer ALM Consultant Dave Burnison brings us this awesome collection of tips and tricks for using PowerShell in VSTS and TFS.


You can now do almost anything in your build and release definitions in VSTS and TFS 2015. One of the most powerful ways to add your specific logic is to use the PowerShell task or the PowerShell on Target Machines task. The documentation that I have found shows you how to use these tasks but, it does not provide many details on how to manage your scripts or write PowerShell scripts that provide feedback to the user via the build and release hubs in Team Web Access. The goal of this blog is to show you how to use some of the commands that are documented here in your TFS Build and/or Release definitions when calling a PowerShell script. I will also show you how to manage shared scripts that can be used across team projects. The examples are from my VSTS account but, as of the time I am writing this, these examples are valid for on-premises TFS as well (TFS 2015 Update 2). For these examples, my source code is stored in TFVC.

Sharing Scripts Across Team Projects

I have a HelloWorld team project that has the application that I am going to build (e.g. $/HelloWorld/Main/HelloWorld.sln) and a Build team project that has common scripts that I want to use in multiple team projects (e.g. $/Build/PSScripts). I can see both source trees in Visual Studio.

clip_image001

When you first define a PowerShell script task it appears that you can only select scripts that live in the current team project. I cannot navigate to $/Build/PSScripts while in the HelloWorld team project.

image

To get around this, let’s first, add a mapping to $/Build/PSScripts on the Repository tab of the HelloWorld – Main build definition to create a Build folder in the local workspace on the build agent.

image

Now I’ll edit my PowerShell tasks. I cannot navigate to $/Build/PSScripts…however, I can manually enter the scripts folder that is now part of the build workspace in the Script filename box. Please note how I am using the $(build.sourcesDirectory) variable in the path to the .PS1 file.

clip_image005

Set a variable in one PowerShell task and access the updated value in subsequent tasks

Using variables in PowerShell scripts is documented here. If you just set the variable like this $env:DynamicVariable = "Temporary Value Set In Script", the scope of the new value is only within the current task, (see “1” in the image below). To set a variable in one PowerShell task and access the updated value in subsequent tasks you need to use a special command: Write-Host "##vso[task.setvariable variable=DynamicVariable]Persistent Value Set In Script", (see “2” in the image below).

clip_image006

Set progress and current operation for current task

My PowerShell script has a long running process so I want to provide a progress indicator in my build output. First I use Write-Host "Begin a lengthy process..." to indicate that I am starting a long running process. Second I use the Write-Host "##vso[task.setprogress value=$i;]Sample Progress Indicator" command to update the progress. The progress indicator is displayed next to the name of the PowerShell script task, (see the PowerShell code inset in the image below).

clip_image007

Displaying Warnings and Errors in the build output

You can leverage the Write-Warning & Write-Error PowerShell commands to display warnings and errors, or you can use the TFS specific task.logissue command to ensure the warnings and errors appears in the build summary. Here are the various commands:

1. # The following will appear in the console output preceded by "WARNING: "
Write-Warning "This is a warning generated in a PowerShell script"

2. # The following will appear Build Summary as a Warning and in the task specific log in YELLOW text.
Write-Host "##vso[task.logissue type=warning;] PowerShell Warning Test"

3. # The following will appear in the Build Summary and the console output with additional error information".
Write-Error "This is an error generated in a PowerShell script"

4. # The following will appear Build Summary as an Error and in the task specific log in YELLOW text.
Write-Host "##vso[task.logissue type=error;] PowerShell Error Test"

Here is what the output looks like in the console window as the build is running.

clip_image008

Here is what the output looks like in the Build Summary after the build has completed.

clip_image009

Bonus Info: Add your own section to the Build Summary output

If you want to add your own section to the Build Summary refer to the VSTS Extension Samples on GitHub, Look specifically at the "Build Results Enhancer" section.

Sample PowerShell Scripts

See the attached file, VSTSPowerShellScripts, for the two scripts I used in my examples above.

I’ve given you a taste of how to make your PowerShell scripts work in harmony with the VSTS/TFS Build and Release systems. For more details on the commands that I have used and additional commands that are available, refer to this documentation on GitHub.

Comments (24)

  1. Brett Jacobson says:

    Where are things like “##vso[task.logissue type=warning;]" documented, besides this blog post? what are the other special text can be used?

  2. Alex Thomas says:

    Gold.

    Anything on PowerShelling VSTS for use with end-to-end SQL BI solutions (RDBMS scripts, SSIS, SSAS and SSRS deploys) would be further awesomeness.

    Thank you

    1. @Alex You can find a number of Azure PowerShell Templates in the “Azure/azure-quickstart-templates” GitHub project. https://github.com/Azure/azure-quickstart-templates

      I would suggest using the searchable template index at https://azure.microsoft.com/en-us/documentation/templates/

      Here are a couple of searches to get you started:
      https://azure.microsoft.com/en-us/documentation/templates/?term=SQL
      https://azure.microsoft.com/en-us/documentation/templates/?term=SSRS

      You should be able to use/adapt these PS templates to work with VSTS Release.

  3. Jan says:

    Good tips!

    When using git, would it be possible to access scripts from a sibling repository (similarly to what you show in first tip)?

    In a project I need to release to multiple environments. In Environment triggers I can chose either not automated, after release creation or chained. In this case I need to trigger environments more dynamically, for example based on PowerShell logic.
    Is there any command available to trigger another environment within the same release, even if this is set to "No automated deployment"?

    1. Dave Burnison says:

      @Jan,

      My apologies for not responding sooner but, a bit of research was needed to address your questions.

      Regarding “There is no built in way to access a sibling repository.”: Currently there is not a simple solution for this. As a work around I would consider the following:
      1. Use a PowerShell task to call a script that:
      - Changes directories to the $(build.sourcesDirectory)
      - Creates a new folder called “Build” under $(build.sourcesDirectory)
      - Changes directories to the $(build.sourcesDirectory)\Build
      - Calls “git init”
      - Calls “git clone https://VSTSAccountName.visualstudio.com/SomeCollection/Build/_git/BuildUtilsStoredInGit” where the URL points to a Git repository that has a set of scripts that are similar to $/Build/PSScripts in the blog post.
      2. Use additional PowerShell tasks to execute the script(s) as needed from $(build.sourcesDirectory)\Build (e.g. $(build.sourcesDirectory)\Build\PSScripts\ApplyVersionToAssemblies.ps1 )
      3. At the end of your build process add a command line task to call the following command which will remove the local copy of the Git repository
      - RmDir /S /Q “$(build.sourcesDirectory)\Build”
      I thought Git submodules might help here but, the submodules must be contained in the same team project, and you would have to duplicate your "build tools" repository in each team project that needs to consume those scripts, refer to https://www.visualstudio.com/docs/build/define/repository#what-kinds-of-submodules-can-i-check-out-

      Another approach might be to use the TF command line instead of Git to get the code directly from $/Build/PSScripts. Then you could share the scripts between both TFVC and Git based builds.

      Regarding the ability to trigger environments more dynamically, for example based on PowerShell logic:
      You could possibly use Invoke-RestMethod from a PowerShell script, (https://technet.microsoft.com/en-us/Library/hh849971.aspx) to call the new Release Management REST APIs. Refer to the following links for more information:
      https://blogs.msdn.microsoft.com/chandananjani/2016/04/15/using-releasemanagement-rest-apis/
      https://www.visualstudio.com/docs/integrate/api/rm/releases
      https://www.visualstudio.com/docs/integrate/api/rm/releases#start-deployment-on-an-environment
      Please note these APIs are very new, at this point the VSTS features for these APIs are ahead of what is available with TFS 2015 Update 2.

  4. Akshay Jain says:

    Just want to clarify something I spent some time on:
    For setting a persistent variable eg: (##vso[task.setvariable variable=$VariableName]$batchKey), the value needs to be a stand-alone string ($batchKey) in this case. It cannot access an object's data-member (something like $Account.PrimaryAccountKey).
    So set $batchKey to $Account.PrimaryAccountKey and use $batchKey.

    (Not sure if this is intended to work this way)

    1. Sorry for the delay, I was out on vacation when you posted your message.

      To use an object's data member you need to enclose it in $( ), for example: $($Account.PrimaryAccountKey).

      The full line would be:
      Write-Host "##vso[task.setvariable variable=DynamicVariable]$($Account.PrimaryAccountKey)"

  5. Robert McCabe says:

    Pam,
    We've been having issues running Powershell scripts on target machines as part of our release process and are having trouble passing parameters.

    I tried your scripts and noticed they run on the "Powershell" task, but not on the "PowerShell On Target Machine" task. The error we get is from the "Write-Host" command :
    "A command that prompts the user failed because the host program or the command type does not support user interaction."

    This would indicate that these are different environments, which is why we can't get parameters to work.

    1. Robert McCabe says:

      The Write-Host issue is documented on the GitHub site: https://github.com/Microsoft/vsts-tasks/tree/master/Tasks/PowerShellOnTargetMachines.

      1. Kiran says:

        In that case is there an option for using something other than write-host for passing the parameters ???

  6. Brian says:

    Would be great to see something like this current and including tips for writing powershell to run on remote machines. Another comment already mentions that 'write-host' doesn't really work. You also get 'bad' behavior when using write-error. The remote task fails but the error isn't highlighted like it should be, instead you get a long message about the 'continue on error' policy.

    1. Hayden Hancock says:

      Agreed. It appears Write-Host, Write-Information, and Write-Verbose don't work in the PowerShell on Target Machines task. Not really sure why, but this is needed when troubleshooting these types of tasks.

      1. Nemanja Stefanovic says:

        I just came across this, so I may be a little late to the party.
        If you are using Write-Information in your script or function, there is a way to access the information stream when calling it by using the -InformationVariable parameter, and returning that - e.g.
        function test-function{
        [cmdletbinding()]
        param()

        Write-Information 'Coming from Test Function'
        }

        test-function -InformationVariable InfoReturn

        Write-Output $InfoReturn

        This works with the PowerShell on Target Machines task (as an alternative to using the verbose stream)

    2. Dave Burnison says:

      Brian,

      Great suggestion. I'll try to make the time to follow-up with another blog post.

  7. Nikolaj Ravn says:

    It all comes down a very vital omission: Sharing items between Team Projects - preferably Build and Release items. We have a major usage of VSTS with a lot of Team Projects. Still, we need to be able to share Task Groups, Variables and PowerShell scripts. And while I'm at it why not enable Release Definitions picking up from multiple repos in multiple Team Projects?

    1. Dave Burnison says:

      Nikolaj,
      My apologies for not replying sooner. Please see the following links related to recent updates:
      • Clone, export, and import a release definition: https://www.visualstudio.com/en-us/docs/release/author-release-definition/more-release-definition#clonedef
      • Link build artifacts from another team project: https://www.visualstudio.com/en-us/articles/news/2016/nov-23-team-services#link-build-artifacts-from-another-team-project

      Also, if you have a feature request you can add it under one of the following UserVoice topics:
      • Release Management topic: https://visualstudio.uservoice.com/forums/330519-team-services/category/145269-release-management
      • Build: https://visualstudio.uservoice.com/forums/330519-team-services/category/145254-ci-build
      Please start by searching for similar requests, if you find a match you can add your vote to that request.

  8. Micha Oren says:

    Thank you very much, very useful Post

    1. Dave Burnison says:

      Thanks for the feedback!

  9. Assaf Stone says:

    FYI - The Write-Verbose cmdlet *DOES* work in scripts executed with the **PowerShell on Target Machines** vso task.

    You can replace Write-Host with Write-Verbose to run the VSO directives, for example
    ``` Write-Verbose "##vso[task.logissue type=warning;]This is a warning message" will write a warning to the task's log.

  10. Hayden Hancock says:

    Can this same functionality be used for the release process? I tried utilizing this for releases and was not able to get this to work. I receive the following error:

    2017-01-28T16:57:18.8407117Z ##[error]. : The term 'C:\BuildAgents\agent\_work\72b9a1c97\$(build.sourcesDirectory)\Scripts\Stop-WebItem.ps1' is not
    2017-01-28T16:57:18.8407117Z ##[error]recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if
    2017-01-28T16:57:18.8407117Z ##[error]a path was included, verify that the path is correct and try again.

    I assume this is because the release definition does not know where the mapped directory is. My original thought is that for common release functionality, I shouldn't be deploying scripts to the target machine(s). These tasks include things like starting and stopping IIS. Seems like these types of scripts could be reused for pretty much every release, so why publish them for each build? Though, I could be wrong in this assumption. So, a follow-up question would be is what is the best approach for releases?

    1. Dave Burnison says:

      @Hayden,

      In this case you would need something like "$(System.DefaultWorkingDirectory)/Build/PSScripts/KeyVaultTest01.ps1" or a similar path as $(build.sourcesDirectory) is not available in a Release definition.

      Also, some of these commands only work in build definitions. Rather than using the following to display progress:
      Write-Host "##vso[task.setprogress value=10;]Running Deployment"

      You would do something like this:
      echo "power shell task inprogress 10%" > C:\vstsagentmaster\src\status.md
      Write-Host "##vso[task.uploadsummary]C:\vstsagentmaster\src\status.md"
      sleep 20
      echo "power shell task inprogress 50%" > C:\vstsagentmaster\src\status.md
      Write-Host "##vso[task.uploadsummary]C:\vstsagentmaster\src\status.md"
      sleep 20
      echo "power shell task inprogress 100%" > C:\vstsagentmaster\src\status.md
      Write-Host "##vso[task.uploadsummary]C:\vstsagentmaster\src\status.md"

  11. vk says:

    Write-Output "##vso[task.logissue type=error;code=-1;] Error!"

    This is working fine in release definition (Tfs2017), and the script is exiting properly when followed by "exist -1" however I would like to set the task status to fail instead of success. Not sure why it is being set to success in the first place. Any idea how to do this?

  12. Jim says:

    This is all very helpful for local scripts, but how do you get Write-Debug or Write-Host or anything else to appear on the TFS window using the Remote Powershell task? Nothing I can do causes output other than Write-Output, but when the task fails, it throws, and output is lost. Is there a way to make Write-Debug or Write-Host appear on the local build agent output?

Skip to main content