We have a ClickOnce application which we're trying to get running with CI/CD on Azure DevOps.
Currently the ClickOnce Prerequisites are set to 'Download prerequisites from the same location as my application' which is the behaviour we want ideally (this app is installed on a Customer's server and the client PCs download the .NET framework and various custom bootstrappers from that server):
When VS builds on my local development PC it picks up those bootstrapper files (which includes some custom bootstrappers we've written) from the local machine and outputs them to the ClickOnce Publishing folder:
However, this doesn't work on Azure DevOps I get this error about not being able to find the .NET bootstrapper instead:
[error]C:\Program Files (x86)\Microsoft Visual
Studio\2017\Enterprise\MSBuild\15.0\Bin\Microsoft.Common.CurrentVersion.targets(5390,5):
Error MSB3152: To enable 'Download prerequisites from the same
location as my application' in the Prerequisites dialog box, you must
download file 'DotNetFX462\NDP462-KB3151800-x86-x64-AllOS-ENU.exe' for
item 'Microsoft .NET Framework 4.6.2 (x86 and x64)' to your local
machine. For more information, see
http://go.microsoft.com/fwlink/?LinkId=616018.
As you'd expect, if I untick .NET 4.6.2 as I prerequisite I no longer get an error about .NET, but weirdly I don't then get an error for our custom bootstrappers, even though those don't exist on Azure either. The difference there seems to be that those custom bootstrappers don't currently exist on my local PC either, so they are shown with yellow exclamation triangles on my local machine.
So, is the .NET bootstrapper definitely not in the Azure build server image that gets spun up, or is it perhaps just a different path?
Or failing that, is there any way to tell DevOps to ignore this issue and just carry on building the actual ClickOnce application files and complete the build? The bootstrappers are already installed on our customer's server, so I don't actually need Azure to bundle them in the output.
Although not the best solution, the following worked for me:
I created an artifacts feed
I used Azure Artifacts with Universal packages to post my bootstrap packages
In the pipeline I used the "Universal packages" task to download the packages in "$ (System.DefaultWorkingDirectory)\bootstrapper"
I added the argument "/p:GenerateBootstrapperSdkPath=$(System.DefaultWorkingDirectory)\bootstrapper" to msbuild
The biggest drawback with this is the space used.
To publish the packages you will need:
Azure CLI
Azure DevOps extension for the Azure CLI: Run "az extension add --name azure-devops" when you have Azure CLI installed
Prepare the packages
In addition to your packages you need to upload the "Engine" directory which is where the setup.bin file is located. In my case I took it from "C:\Program Files (x86)\Microsoft SDKs\ClickOnce Bootstrapper"
I copied the "Engine" and "Schemas" directories to a temporary directory and then executed:
az login
az artifacts universal publish ^
--organization https://dev.azure.com/<MY-ORGANIZATION>/ ^
--project = "<MY PROJECT>" ^
--scope project ^
--feed <MY-FEED> ^
--name clickonce ^
--version 16.0.28315 ^
--description "ClickOnce Bootstrapper" ^
--path "D:\temp\ClickOnce Bootstrapper"
In the parameters "name", "version" and "description" you can put what you want, the important thing is to identify the package.
If the feed you created is associated with the organization, the command should not include the parameters "project" or "scope", you can see an example on how to publish by clicking on the "Connect to feed" button in your artifacts feed.
Then I ran the same command for each package I require, for example:
az artifacts universal publish ^
--organization https://dev.azure.com/my-organization/ ^
--project = "my project" ^
--scope project ^
--feed my-feed ^
--name dotnet ^
--version 4.6.2 ^
--description "Microsoft .Net Framework 4.6.2" ^
--path "C:\Program Files (x86)\Microsoft SDKs\ClickOnce Bootstrapper\Packages\DotNetFX462"
The pipeline
Before the compilation task add the download of the packages:
First download the engine, in the case of this example it would be the clickonce package 16.0.28315
- task: UniversalPackages#0
inputs:
command: 'download'
downloadDirectory: '$(System.DefaultWorkingDirectory)\bootstrapper'
feedsToUse: 'internal'
vstsFeed: '<MY FEED ID>'
vstsFeedPackage: '<MY PACKAGE ID>'
vstsPackageVersion: '16.0.28315'
Of course, with the wizard it is much easier, find the "Universal packages" task, select the "Download" command and fill in the rest of the parameters.
For your packages it is similar, just change the download path to "$(System.DefaultWorkingDirectory)\bootstrapper\Packages<PACKAGE NAME>"
The important thing at this point is that if you already have the packages published on a website, the "Package name" is the same name of the directory where the package is currently published, since the setup will try to download it from that path.
Lastly, my compilation task looks something like this:
- task: VSBuild#1
inputs:
solution: '$(solution)'
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
msbuildArgs: '/target:publish /p:GenerateBootstrapperSdkPath=$(System.DefaultWorkingDirectory)\bootstrapper'
Of course, this makes a lot more sense with your own packages. It works with the .Net Framework, but it will use some valuable MB. An alternative could be to create a script, in PowerShell for example, that downloads and installs the .Net Framework. Another option, if you have one available, is to copy the files from an FTP or fileserver, the latter applies to both the .Net Framework and its own packages, the only thing relevant is to redirect the Bootstrappers path with the parameter "GenerateBootstrapperSdkPath", about how packets get into that directory, you have several options.
Related
Background
As part of our deployment pipeline we are creating our deployment artifact, by running several .xdt transforms on our build artifact as well as adding several additional files.
As the last step before publishing, we would like to invoke msdeploy.exe to build a "custom" webdeploy package from a folder containing the wwwroot-content - (msdeploy command for creating custom package found in this question Web Deploy - How to create a package with selected items)
We are using hosted agents (win 2017).
We wish to deploy to an Azure AppService.
Question
Is there a task in Azure DevOps, that allows you to invoke MsDeploy.exe manually, such that we can create a custom webdeploy package, before we deploy?
Is there a task in Azure DevOps, that allows you to invoke MsDeploy.exe manually, such that we can create a custom webdeploy package, before we deploy?
I am afraid there is no such task to invoke MsDeploy.exe manually. We need invoke it by command line task, just like Daniel comment.
As we know, the default installation will place msdeploy.exe in:
C:\Program Files (x86)\IIS\Microsoft Web Deploy V3\msdeploy.exe
To verify the msdeploy path on the hosted agents, I use a copy task with content **\msdeploy.exe:
Then use the Publish build artifacts to output the msdeploy.exe, I could get the result on the hosted agent vs2017-win2016 and windows-2019:
So, the the msdeploy path on the hosted agents vs2017-win2016 and windows-2019 is C:\Program Files (x86)\IIS\Microsoft Web Deploy V3\msdeploy.exe. We could use command line task to invoke it.
Hope this helps.
Here is the exact CommandLine task that worked for me (without parameters though):
I have an Azure DevOps project (just one).
I have a Build Pipeline set to run in the "Hosted VS2017" Agent Pool. This Agent Pool appears to be in the [MyProject]\Build Administrators, Contributors, Project Administrators, and Release Administrators roles.
I also have an Artifacts nuget feed in the DevOps project. It has [MyProject]\Project Valid Users set as "Reader" role. It appears that Project Valid Users has all of the Agent Pool's roles mentioned above as members.
I have an azure-pipelines.yml script that adds that adds the artifacts feed as a nuget source right at the beginning:
# Add nuget source
- powershell: Invoke-RestMethod "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -OutFile "$env:UserProfile/nuget.exe"
- script: '%UserProfile%\nuget.exe sources Add -Name "devops" -Source "https://pkgs.dev.azure.com/MyProject/_packaging/feed/nuget/v3/index.json"'
The build yml then dot a dotnet build but fails inside NuGet.targets with:
Unable to load the service index for source https://pkgs.dev.azure.com/MyProject/_packaging/feed/nuget/v3/index.json.
Response status code does not indicate success: 401 (Unauthorized).
how can I make this work? My build needs packages from other builds that are on that artifacts feed...
There is a better alternative imo. You can continue using your script to dotnet restore.
Simply add a task just before that with NuGetAuthenticate#0
steps:
- task: NuGetAuthenticate#0
- script: dotnet restore --no-cache --force
this task will authenticate the pipeline with the nuget feeds that require so and are found at the NuGet.config file.
More info here
Notice that when the nuGet feed is within Azure DevOps there is nothing else required. If the feed is external you can configure at your Azure DevOps a nuGet Service Connections (at the link there is further info).
Use the built-in tasks for installing and running NuGet and you won't have authentication problems.
Use the dotnet task's restore command. If you're using a single Azure Artifacts feed, just select it from the dropdown in the task (instead of the PowerShell you mentioned). If multiple feeds (doesn't look like it from your question, but just in case), you'll need to check in a NuGet.config that references all of those feeds, then point the task to that config.
You may also need to pass the '--no-restore' flag to your 'dotnet build' command.
If you still encounter issues, ensure the correct build identity has access to your feed.
I was using publish to folder option through Visual Studio by right-clicking on the project -> publish -> publish to folder. Result was always ready-to-copy project with applied transformations.
I wanted to automate this process using VSTS and have setup build on VSTS.
I used next steps:
- NuGet restore
- Build solution
- Publish Build Artifacts to $(build.artifactstagingdirectory)
- Windows machine file copy from $(build.artifactstagingdirectory) to remote machine using admin login and password
And finally I'm getting zip package on remote machine with complicated folder structure without applied transformations inside at all.
What is wrong? How I can setup same "publish to folder" as in Visual Studio but using VSTS?
Add below Target to your .csproj to enable transforming config files
<Target Name="TransformConfigFiles" AfterTargets="AfterBuild" Condition="'$(TransformConfigFiles)'=='true'">
<ItemGroup>
<DeleteAfterBuild Include="$(WebProjectOutputDir)\Web.*.config" />
</ItemGroup>
<TransformXml Source="Web.config" Transform="$(ProjectConfigTransformFileName)" Destination="$(WebProjectOutputDir)\Web.config" />
<Delete Files="#(DeleteAfterBuild)" /></Target>
In your build solution step add the following build arguments "/p:TransformConfigFiles=true" will make the config transformation using the above added target to .csproj
/p:TransformConfigFiles=true /p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:OutDir="$(build.stagingDirectory)"
Then you can use a publish step to publish your $(build.stagingDirectory) contents. You can use $(build.stagingDirectory)_PublishedWebsites as path to publish if you only need the website output.
This will allow you to get the ms deploy package as well as xcopy deploy published website files.
You can use copy files task before the publish task to copy any additional files if you have any to $(build.stagingDirectory) and get them published as build artifacts.
Use VSTS release management with deployment groups to deploy your application to target server. You can use IIS deploy task to deploy to IIS using ms deploy package. If you are using web deploy package you can use a parameters.xml in your web app to get the web config parameters assigned to .setparameters.xml so that you can change values in the deployment time using IIS deployment task.
You are publishing web application through File System method, it is based on the specified configuration (e.g. Debug, Release) to transform web.config. So you need to check which configuration you specified in build solution task (e.g. Visual Studio Build task)
Simple tasks:
NuGet Tool Installer task
NuGet restore task
Visual Studio Build task (MSBuild Arguments: /p:SkipInvalidConfigurations=true /p:DeployOnBuild=true /p:WebPublishMethod=FileSystem /p:publishUrl="$(build.artifactstagingdirectory)\\" /p:DeployDefaultTarget=WebPublish; Platform: $(BuildPlatform); Configuration: $(BuildConfiguration)) Note: BuildPlatform and BuildConfiguration are build variables. It will publish web app to artifacts directory ([agent working folder]/1/a)
Publish Build Artifacts (Path to publish: $(build.artifactstagingdirectory))
How can I achieve CD (Continuous Delivery) for winform applications in VSTS (Visual Studio Team Services)? Currently this is what I have in my Visual Studio Solution file
1) A winform project
2) A Windows setup and Deployment project
So every time I build a winform project, I do the following steps (and I need CI / CD for exactly these)
1) Build Setup and Deployment project, which takes Build output of Winform project and creates and EXE / MSI
2) I take this MSI file and use NSIS to embed it inside EXE
3) I run SIGNTOOL from command prompt and digital sign the EXE
4) I upload this signed EXE to my website
Now how can I use CI / CD pipeline to automate the above or is it not possible for my case? I am confused. I can't find any material for winforms, all are for web apps.
Thanks
You will obviously need some sort of desktop deployment strategy. The easiest is to be using xcopy. Other alternatives include frameworks like ClickOnce, Windows Installer or Squirrel to name a few. I have a number of corporate apps that use Clickonce that I have deployed using vsts.
Now I am unable to understand how will VSTS help me with this?
Use VSTS to build the software first and include additional tasks to package your app. In my case, I use devenv.exe to generate ClickOnce packages, but you can include custom tasks by using powershell. The artifact of the build should now be the "packaged app".
Then use the VSTS deployment to copy the "package" to some kind of hosting server from where your users can download the package. That could be either a web server or a fileserver or any location appropriate for your deployment strategy.
In this context, VSTS is an orchestration tool. It helps to trigger actions for you.
See Deploy an agent on Windows to see how to setup an on-premise agent.
To build and deploy the way as you used in VSTS, you can use below steps:
Create a repository (Git or TFVC) and push your solution in the repository.
Add build/release definitions.
With CI build, enable the Continuous Integration in Triggers Tab. With CD deploy, enable Continuous deployment trigger in Pipeline Tab. The process for CI build and CD deploy, you can refer CI/CD.
Add related tasks in your build/release definition.
Build VS Installer task: build setup project with msi file.
Nsis Build Task: embedded msi file in exe.
Command Line task: to execute the signtool command. Since Hosted agent has not signtool.exe, so you should use private agent which has the signtool.exe on the machine.
Copy files task, Copy Files Over SSH task or Windows Machine File copy task: upload the file exe to your web server.
I am using a LinqPad script to automate an internal health check via AppVeyor.
The script references a custom nuget package hosted on our appveyor account.
The build does the following:
Pulls down a GitHub repo
via LPRUN executes health-check.linq
Locally this works.
On AppVeyor it does not.
I have the following build process
SETUP
Via chocolatey --> install Linqpad5
choco install linqpad5
BUILD
nuget install Example.Package
[this is our own NUGET package hosted on AppVeyor [SUCCESS]]
xcopy "c:\projects\example_project\utilities" %AppData%\LINQPad\ /i
[copy our custom NuGetSources.xml file containing our nuget repo location to linqpads folder]
cd "C:\Program Files (x86)\LINQPad5\"
lprun "C:\projects\example_project\utilities\health-check.linq"
ERROR
'Error downloading 'Example.Package' - An error occurred while retrieving package metadata for 'Example.Package' from source 'Example Company Repo'.
Does anyone have any hints on how to reference a custom NUGET repo from a LINQPAD script on APPVEYOR?
More Info
We use AppVeyor for our CI. It allows us to write our own custom NUGET packages for internal use within our own projects.
We have a repository ('FinPad') that contains numerous .linqpad files that automate our processes & house keeping.
Each FinPad script contains a reference to a package called 'FairGo.FinPower' on our own AppVeyor nuget repo. This custom nuget package contains numerous 3rd party .Net DLLs & our own custom code to connect to a 3rd party financial loan management system we use as a backend - http://www.finpower.com.au/ (hosted by us on Azure)
One such script is a 'Health Check' - this confirms a specific environment is operating OK.
For our TEST environment I wanted to schedule our 'health check linqpad' script to run every 15 minutes (and on failure alert stackify & slack)
The process works as follows ( using a custom Azure build machine from AppVeyor)
every 15 minutes
run a custom AppVeyor build called 'Health Check TEST' -->
pull down the GitHub project 'FinPad' to c:\projects\finpad
before build --> choco install linqpad5
Run the below command to copy our own nuget.config (with our own FairGo nuget repo references) to the location linqpad wants for reference
copy "c:\projects\finpad\utilities\nuget.config" "C:\Users\appveyor\AppData\Roaming\NuGet"
*** the above nuget.config contains the url / username /password to our appveyor nuget repo in plain text
build --> cd "C:\Program Files (x86)\LINQPad5\"
after build --> lprun "C:\projects\FinPad\utilities\health-check-test.linq"
For reference the Health check linqpad file is
http://share.linqpad.net/tuthme.linq
Locally this works fine (I manually configured our NUGET repo by the LinqPad GUI locally & assumed it only updated the nuget.config in 'C:\Users\ME\AppData\Roaming\NuGet\nuget.config'. (so I added this file to the repo & copy it every build)
On AppVeyor the build gives the below error
Downloading NuGet package FairGo.FinPower and dependencies from https://api.nuget.org/v3/index.json
Error downloading 'FairGo.FinPower' - Unable to find package 'FairGo.FinPower'.
Command exited with code 1
I have this running through a Console App now - but would really like to get this working through a LinqPad script if possible.