Deploying .NET Applications Learning MSBuild and ClickOnce
Sayed Y. Hashimi and Sayed Ibrahim Hashimi
Deploying .NET Applications: Learning MSBuild and ClickOnce Copyright © 2006 by Sayed Y. Hashimi, Sayed Ibrahim Hashimi All rights reserved. No part of this work may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording, or by any information storage or retrieval system, without the prior written permission of the copyright owner and the publisher. ISBN-13 (pbk): 978-1-59059-652-4 ISBN-10 (pbk): 1-59059-652-8 Printed and bound in the United States of America 9 8 7 6 5 4 3 2 1 Trademarked names may appear in this book. Rather than use a trademark symbol with every occurrence of a trademarked name, we use the names only in an editorial fashion and to the benefit of the trademark owner, with no intention of infringement of the trademark. Lead Editor: Jonathan Hassell Technical Reviewer: Bart De Smet Editorial Board: Steve Anglin, Ewan Buckingham, Gary Cornell, Jason Gilmore, Jonathan Gennick, Jonathan Hassell, James Huddleston, Chris Mills, Matthew Moodie, Dominic Shakeshaft, Jim Sumser, Keir Thomas, Matt Wade Project Manager: Richard Dal Porto Copy Edit Manager: Nicole LeClerc Copy Editor: Kim Wimpsett Assistant Production Director: Kari Brooks-Copony Production Editor: Ellie Fountain Compositor: Kinetic Publishing Services, LLC Proofreader: Dan Shaw Indexer: Brenda Miller Artist: Kinetic Publishing Services, LLC Cover Designer: Kurt Krames Manufacturing Director: Tom Debolski Distributed to the book trade worldwide by Springer-Verlag New York, Inc., 233 Spring Street, 6th Floor, New York, NY 10013. Phone 1-800-SPRINGER, fax 201-348-4505, e-mail
[email protected], or visit http://www.springeronline.com. For information on translations, please contact Apress directly at 2560 Ninth Street, Suite 219, Berkeley, CA 94710. Phone 510-549-5930, fax 510-549-5939, e-mail
[email protected], or visit http://www.apress.com. The information in this book is distributed on an “as is” basis, without warranty. Although every precaution has been taken in the preparation of this work, neither the author(s) nor Apress shall have any liability to any person or entity with respect to any loss or damage caused or alleged to be caused directly or indirectly by the information contained in this work. The source code for this book is available to readers at http://www.apress.com in the Source Code section.
To my parents, Sayed A. and Sohayla Hashimi, and to my wife and daughter, Farishta and Fairoza. —Sayed Y. Hashimi To my parents, Sayed A. and Sohayla Hashimi, because this would have not been possible without their support and guidance. —Sayed Ibrahim Hashimi
Contents at a Glance About the Authors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi About the Technical Reviewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvii
■CHAPTER ■CHAPTER ■CHAPTER ■CHAPTER ■CHAPTER ■CHAPTER ■CHAPTER ■CHAPTER ■CHAPTER
1 2 3 4 5 6 7 8 9
Deployment Prerequisites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 The Unified Build Engine: MSBuild. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 MSBuild: By Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 Extending MSBuild . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Introducing Team Foundation Server and Team Build . . . . . . . . . . 107 Deploying Smart Clients with ClickOnce . . . . . . . . . . . . . . . . . . . . . . . 137 ClickOnce Updates, Security, and the Bootstrapper . . . . . . . . . . . . 161 The ClickOnce Data Directory and Deploying Prerequisites . . . . . 185 ClickOnce Tools and Scenarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
■INDEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
v
Contents About the Authors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi About the Technical Reviewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvii
■CHAPTER 1
Deployment Prerequisites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Types of Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Windows Forms (Smart Client) Applications . . . . . . . . . . . . . . . . . . . . . 2 Web Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 Smart Device Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Windows Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Console Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Hosted Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Application Architectures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 Client-Server Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 N -Tier Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Service-Oriented Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 Strategies for Deploying the .NET Framework . . . . . . . . . . . . . . . . . . . . . . . 17 Where Do You Need the .NET Runtime? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Typical Deployment of a Smart Client . . . . . . . . . . . . . . . . . . . . . . . . . 18 Typical Deployment of a Thin Client . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
■CHAPTER 2
The Unified Build Engine: MSBuild . . . . . . . . . . . . . . . . . . . . . . . . . 21 Introducing Build Tools and Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Make-Style Build Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Ant/NAnt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Introducing MSBuild . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 Targets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Tasks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 vii
viii
■CONTENTS
■CHAPTER 3
MSBuild: By Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 Introducing Well-Known Metadata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 Formatting Your Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 Editing MSBuild Files with IntelliSense . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 Integrating MSBuild into Visual Studio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Introducing Custom Metadata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 Understanding the Difference Between @ and % . . . . . . . . . . . . . . . . . . . . 59 Using Environment Variables in Your Project. . . . . . . . . . . . . . . . . . . . . . . . . 63 Reusing MSBuild Project Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Dealing with MSBuild Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
■CHAPTER 4
Extending MSBuild . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Logging with MSBuild . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Writing a Logger. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 Using NUnit and MSBuild . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
■CHAPTER 5
Introducing Team Foundation Server and Team Build . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 Introducing Visual Studio Team System (VSTS) . . . . . . . . . . . . . . . . . . . . . 107 Introducing Team Build . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 Introducing the Team Foundation Build Architecture . . . . . . . . . . . . . . . . . 108 Team Foundation Build Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 Application Tier. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 Data Tier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 Build Machine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 Drop Location . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 Using Team Foundation Build . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 Creating a New Team Project. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 Understanding the Team Project Fundamentals. . . . . . . . . . . . . . . . . . . . . 112 Work Items . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 Documents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 Reports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 Team Builds . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 Source Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 Placing Code in Source Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
■CONTENTS
Using Team Build . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 Understanding How Team Build Works . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 Extending the Team Build. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 Handling Errors During a Team Build . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 Automating Team Build. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
■CHAPTER 6
Deploying Smart Clients with ClickOnce . . . . . . . . . . . . . . . . . 137 Introducing Side-by-Side Deployment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 Looking at the Previous Approaches of Deploying Windows Forms Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 MSI Deployment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 No-Touch Deployment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 The Updater Application Block. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 Introducing ClickOnce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 Introducing the ClickOnce Deployment Methods . . . . . . . . . . . . . . . . . . . . 145 Introducing the ClickOnce Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 Seeing ClickOnce in Action . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 Updating and Versioning with ClickOnce . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 Introducing ClickOnce Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 Customizing Deployment with the ClickOnce API . . . . . . . . . . . . . . 155 Understanding the Bootstrapper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
■CHAPTER 7
ClickOnce Updates, Security, and the Bootstrapper . . . . . 161 Understanding the ClickOnce Manifest Files . . . . . . . . . . . . . . . . . . . . . . . 161 Offline vs. Online Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 Performing ClickOnce Updates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 Configuring Update Notification. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 Configuring Application Update Policy . . . . . . . . . . . . . . . . . . . . . . . . 167 Understanding ClickOnce Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 Using Trusted Publishers in ClickOnce . . . . . . . . . . . . . . . . . . . . . . . . 170 Seeing a Trusted Publisher in Action . . . . . . . . . . . . . . . . . . . . . . . . . 172 Introducing Code Access Security (CAS) . . . . . . . . . . . . . . . . . . . . . . 173 Introducing Partially Trusted Applications with ClickOnce . . . . . . . 175 Full Trust vs. Partial Trust . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 Available Permissions vs. Actual Permissions. . . . . . . . . . . . . . . . . . 181 Deploying Prerequisites with ClickOnce . . . . . . . . . . . . . . . . . . . . . . . . . . . 181 Using the Bootstrapper to Install Prerequisites. . . . . . . . . . . . . . . . . 181 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
ix
x
■CONTENTS
■CHAPTER 8
The ClickOnce Data Directory and Deploying Prerequisites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 Working with Application Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 Working with Data Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190 Considering Security When Using the ClickOnce Data Directory . . . . . . 200 Working with Prerequisites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201 Understanding the Prerequisite Manifest Files . . . . . . . . . . . . . . . . . 205 Building a Custom Prerequisite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
■CHAPTER 9
ClickOnce Tools and Scenarios
. . . . . . . . . . . . . . . . . . . . . . . . . . . 219
Using the Bootstrapper Manifest Generator (BMG) . . . . . . . . . . . . . . . . . . 219 Using the Manifest Generation and Editing (MAGE) Tool. . . . . . . . . . . . . . 228 MAGE Scenario: ClickOnce Application Has to Be Deployed to More Than One Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 MAGE Scenario: The Producer of the Application Doesn’t Know Where the Application Will Be Hosted for Deployment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 MAGE Scenario: ClickOnce Application Has to Delay-Sign Assemblies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230 MAGE Scenario: ClickOnce Application Assemblies Need to Be Obfuscated . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231 Creating the ClickOnce Manifest Files with the MAGE Tool . . . . . . . . . . . 232 Creating the Application Manifest . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 Creating the Deployment Manifest . . . . . . . . . . . . . . . . . . . . . . . . . . . 235 Using MSBuild with ClickOnce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 Creating the ClickOnce Deployment Using MSBuild . . . . . . . . . . . . 237 Using MSBuild and ClickOnce As Part of a Build Process . . . . . . . . 239 Looking at Some Common ClickOnce Scenarios . . . . . . . . . . . . . . . . . . . . 245 Passing Parameters to a ClickOnce Application . . . . . . . . . . . . . . . . 245 Installing the Publisher Certificate Programmatically with a Prerequisite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246 Creating File Type Associations for ClickOnce Deployments . . . . . 249 Creating a Desktop Icon for a ClickOnce-Deployed Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 Requiring a Prerequisite After the Initial Install. . . . . . . . . . . . . . . . . 250 Deploying a ClickOnce Application from a CD/DVD . . . . . . . . . . . . . 251 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
■INDEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
About the Authors ■SAYED Y. HASHIMI was born in Kabul, Afghanistan, and now resides in Jacksonville, Florida. Sayed has expertise in the areas of healthcare, banking, logistics, scientific computing, and civil/structural engineering. In his professional career, Sayed has developed large-scale distributed applications with a variety of programming languages and platforms, including C++, Java, and .NET. He has published articles in major software journals and is the principal author of Pro Service-Oriented Smart Clients with .NET 2.0 (Apress, 2005). Sayed has a master’s degree in engineering from the University of Florida. You can reach Sayed by visiting http://www.sayedhashimi.com.
■SAYED IBRAHIM HASHIMI has a computer engineering degree from the University of Florida. He works in Jacksonville, Florida, as a developer and architect. He is an expert in the financial, education, and collection industries. His primary focus is working with .NET, but he also has extensive industrial experience with Java-based technologies. Sayed’s research interests include a wide range of topics including computer graphics, peer-to-peer technologies, and lucid dreaming. You can read Sayed’s blog at http://www.sedodream.com. When he’s not busy creating software or dreaming, you’re likely to find him at the local coffee shop.
xi
About the Technical Reviewer ■BART DE SMET was born on February 11, 1983, in Belgium and has a master’s degree in computer science from Ghent University. Since early 2000, Bart has been involved in the wonderful world of .NET and is also a Visual C# MVP. While keeping his brain busy with further university studies, he focuses on C#, the CLR, SQL Server 2005, and WinFX. Regularly you can find Bart speaking at various European Microsoft events, and if time permits, he writes articles for the local MSDN Web site. To read about his adventures in the .NET galaxy, check out Bart’s blog at http://blogs.bartdesmet.net/bart.
xiii
Acknowledgments W
riting this book took effort from not only the authors but also from some of the very talented staff at Apress. Therefore, we would like to thank Jonathan Hassell, Richard Dal Porto, Ellie Fountain, and Kim Wimpsett. We would also like to acknowledge the technical reviewer, Bart De Smet, for taking the time to review the book. His corrections and commentary were invaluable.
xv
Introduction T
his book covers two important aspects of the software life cycle: build and deployment. The coverage of these crucial topics is only half the attraction of this book, though. The other half is the technologies covered: MSBuild and ClickOnce.
What Is MSBuild? Previously the build process that Visual Studio followed was basically a black box and was difficult to customize. With the arrival of Visual Studio 2005 and .NET 2.0 comes the arrival of the Microsoft Build Engine, otherwise known as MSBuild. MSBuild is the utility that Visual Studio uses to build your managed (C#, VB .NET, and J#) projects. MSBuild is an XML-based build engine and a tool that has been developed with customizability and extensibility in mind from its conception. By using MSBuild, you can change how your projects are built, creating customizations to fit your needs. With the advent of MSBuild, you no longer have to rely on third-party tools to handle the custom aspects of building your application. In addition, not only is this approach supported by Microsoft, but it is completely integrated with Visual Studio. If you need to tweak the settings for the C# compiler or how resources are generated, you now have this ability. The entire build process is open, and you can customize it in any way necessary. With other third-party tools this is simply unachievable. In this book, we will start with MSBuild concepts that you need to know in order to get started, and then we will cover some advanced topics. Over the course of a few chapters we will take you from an MSBuild newbie to an MSBuild expert! For example, we will discuss how to inject custom steps into the build process, how to create custom tasks, and much more. We will cover all of the necessary topics that you need to know in order to use MSBuild in all the great ways that it was intended.
What Is ClickOnce? For more than a decade now, technology decision makers have implemented business processes using “the disconnected Web” simply because Web applications are easy to deploy. If you perform a feature-by-feature comparison of a Web application versus a desktop application (such as a Windows Forms application), you’ll be amazed to see what you bypass just to have something easily deployed (see Table 1).
xvii
xviii
■INTRODUCTION
Table 1. Web Application vs. Desktop Application
Feature
Desktop Application
Web Application
Has interactive and stateful user interface?
Yes
No
Offers offline support?
Yes
No
Uses desktop resources?
Yes
No
Is easy to deploy?
No
Yes
Organizations have repeatedly given up interactive and stateful applications just so they can easily deploy them. In addition, organizations have repeatedly given up all the benefits of having access to a workstation’s local resources so they can easily deploy applications. Finally, organizations have repeatedly given up the benefits of having applications function without a server connection so they can easily deploy them. You don’t have to do this anymore. ClickOnce, finally, solves the complicated problem of “easily deploying a desktop application” and gives the desktop back to you. ClickOnce enables you to deploy Windows Forms applications just like you deploy Web-based applications. In addition, ClickOnce provides automatic updates and traditional features found in a Windows Installer, without the disadvantages. For example, the ClickOnce technology can add entries to the user’s Start menu and provides an icon in Add/Remove Programs for the user to uninstall the application. It does this without requiring users to be administrators on their workstation. ClickOnce provides all of this out of the box. With ClickOnce you get the ease of deployment of a Web application along with the following additional benefits inherent in a Windows Forms application: • Web-based installation • Automatic and configurable updates via a URL • Installation without administrator privileges (users don’t have to be admins to install ClickOnce applications) • Automatic rollback facilities and traditional desktop installations (such as a menu item under the user’s Start menu) These features, as a whole, have not been available to thick client applications in the past. With ClickOnce, organizations can return to offering dynamic applications that interact with the user’s desktop (for example, with Microsoft Office, a printer, a network, and so on) while providing easy installation and automatic updates.
Who Should Read This Book? This book was written for developers and deployment engineers working with .NET 2.0 on the Windows platform. Developers will benefit from reading this book because build and deployment are fundamental aspects of writing and testing software. Deployment engineers will benefit from reading this book because ClickOnce is now the recommended deployment model for Windows Forms applications and because MSBuild is now the unified build engine for the Windows platform.
■INTRODUCTION
What’s in This Book? This book covers build and deployment using MSBuild and ClickOnce. The book is broken up into two parts; the first part (Chapters 2–5) covers MSBuild, and the second part (Chapters 6–9) covers ClickOnce. Here is a breakdown of each chapter: Chapter 1, “Deployment Prerequisites”: Most large organizations have a team dedicated to build and deployment. Individuals on a team like this are not developers. In this chapter, we’ll assume you are not a developer and give you the proper background required to do build and deployment. We’ll talk about .NET, application architecture, and various types of applications. The goal of this chapter is to help you to understand what you can expect to build and deploy. Chapter 2, “The Unified Build Engine: MSBuild”: In previous versions of Visual Studio, the build process was mostly a black box; because of this, performing customizations to the build process was not very easy. With the new versions of Visual Studio and the .NET Framework, the build process is fully exposed and documented. It is easy to fine-tune the steps that will be followed when your projects are built. MSBuild is an XML-based build system; in this chapter, we’ll introduce MSBuild and its fundamental concepts. Chapter 3, “MSBuild: By Example”: In Chapter 2, we’ll outline the fundamentals of MSBuild. Like with many other technologies, it is easier to get a feel for MSBuild when you see it in use in different scenarios. The aim of this chapter is to provide real examples that will provide a concrete foundation to your MSBuild knowledge. Topics vary from how to use MSBuild item metadata to the difference between the @ syntax and the % notation. Chapter 4, “Extending MSBuild”: MSBuild is a system with extensibility as a focal point from its conception. Two aspects that MSBuild provides are flexible and powerful extensions: custom tasks and custom loggers. In this chapter, we present a real-world custom task from the ground up. This task and its accompanying targets file are responsible for executing any NUnit tasks that are contained in the built assemblies. As a sample of a real-world logger, we’ll show how to create a custom XML logger. Chapter 5, “Introducing Team Foundation Server and Team Build: With this version of Visual Studio, Microsoft has made some other tools available. One of these tools is the Team Foundation Server (TFS). A part of TFS is a new source control management tool. When using TFS, you can also use Team Build, which is a utility that can create, maintain, and execute public builds. For enterprise organizations, creating and verifying a public build is a critical component of projects. With TFS and Team Build, you can achieve this. In this chapter, we’ll introduce the necessary concepts to use TFS and Team Build to create and customize your public build. Chapter 6, “Deploying Smart Clients with ClickOnce”: This chapter opens the second part of the book—deploying Windows Forms applications with ClickOnce. This chapter is an overview of what ClickOnce is. We’ll start by building the case for why ClickOnce is important. We’ll talk about some of the technologies that tried to do the same thing but failed. We’ll give short introductions to how ClickOnce supports automatic updates. We’ll also cover how to handle the sensitive issue of giving an application the proper privileges to do what it needs on the client.
xix
xx
■INTRODUCTION
Chapter 7, “ClickOnce Updates, Security, and the Bootstrapper”: This chapter tells you everything you need to know about ClickOnce updates, security, and the generic bootstrapper. We’ll start by dissecting the deployment and application manifest files. We’ll then jump into how an application is configured for updates and when and how an application is updated in the background. After you understand the details of ClickOnce updates, we’ll talk about ClickOnce security. Historically, thick client applications that have a client-side footprint have always been restricted to a security sandbox. This sandbox either was not configurable at all or was configurable in a way that was not practical. After reading this chapter, you’ll see how ClickOnce solves this problem in a practical manner. The last topic we’ll talk about in this chapter concerns getting application prerequisites deployed with your ClickOnce applications, which will prep you for the next chapter. Chapter 8, “The ClickOnce Data Directory and Deploying Prerequisites”: Nontrivial business applications today need a way to store application data. Storing application state is not something new and is easily accomplished if you have a connection to your database. But what if your application is a smart client and has to support offline capabilities? In other words, where do you store application state if you don’t have a connection to your database on the network? ClickOnce provides the ClickOnce data directory for you to store application state. The ClickOnce data directory is something special and is managed as you move from one version of your application to the next. This chapter talks about the data directory, offline support, and how to migrate data as your application gets updated. The second portion of this chapter is about deploying custom prerequisites. Visual Studio 2005 comes with a short list of popular packages that you can deploy with your application, but what if you have your own prerequisites that you built or one that is not in the list? How do you deploy your own prerequisites? This chapter will tell you how to do that. Chapter 9, “ClickOnce Tools and Scenarios”: This chapter will talk about three tools that will help in deploying ClickOnce applications and some common ClickOnce scenarios. The tools discussed include the Bootstrapper Manifest Generator (BMG), the Manifest Generation and Editing (MAGE) tool, and MSBuild. The BMG is a Windows Forms application that provides a user interface for building the package and product manifest files that are required to deploy a custom prerequisite. The MAGE tool is a Windows Forms application that helps you build the deployment and application manifest files for your ClickOnce applications. We’ll also talk about how you can automate a ClickOnce deployment—of course, you do this using MSBuild tasks. Finally, we’ll present some common ClickOnce scenarios, covering practical ClickOnce problems and offering possible solutions. After reading this book, you’ll have a good understanding of the fundamentals of MSBuild and ClickOnce. You’ll also understand how to use the two technologies to establish a build and deployment process in your organization.
CHAPTER
1
■■■
Deployment Prerequisites B
uild and deployment are engineering problems, and in most big organizations, these are delegated to an entire team. The members of these teams aren’t necessarily developers, yet they are experts on build and deployment processes and engineering. That is, you do not need to know how to code or know how something was created to be able to build and/or deploy it. And you should not have to know this! Having said that, as a build and deployment engineer, you should have a fundamental understanding of the various types of applications and application architectures. You should, for example, know the types of components in a Web application versus a Windows Forms application. Similarly, you should know the differences between client-server architecture and n-tier architecture. Why is this important? It is important for a deployment and build engineer to be familiar with the various types of applications and application architectures for two reasons. The first and obvious reason is that in order to build an application or deploy it, you need to know what components it contains. For example, with a Web application, it helps to know it has a configuration file and you may have to modify this file when you write a build script for it. With a client-server application, it helps to know it has a client-side deployment and a server-side deployment. Having this knowledge helps you do your job better. The second important reason is that often deployment engineers have to perform a basic level of testing after deploying an application. Having some basic knowledge about the type of application and its architecture can go along way to resolving some fundamental problems. In this chapter, we will define the various types of applications and briefly describe commonly used application architectures. We will also define the .NET Framework and describe methods of deploying the .NET runtime.
Types of Applications With the .NET Framework, you can build Windows Forms applications, Web applications, Web services, smart device applications, Windows services, console applications, and hosted applications: Windows Forms applications: Windows Forms applications are applications with a graphical user interface (GUI) front end, and they run on desktops. Examples of this type of application include Microsoft Word, Microsoft Excel, and so on.
1
2
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
Web applications: Web applications are applications built with ASP.NET. These applications have a server-side component and a client-side component. The server-side contains the business logic, and the client-side contains the view (GUI) that is displayed in a browser. Web applications are accessed via a uniform resource locator (URL)—for example, http://www.sayedhashimi.com. Web services: Web services are standards-based systems accessible over a network such as the Internet. Web services are generally employed to connect disparate systems. Web services are sometimes called XML Web Services. Smart device applications: Smart device applications are applications that target mobile devices (for example, Smartphone devices). Smart device applications are built with the .NET Compact Framework, a subset of the .NET Framework. Windows services: Windows services are executables that run in the background. The special feature of Windows services is that they don’t require an interactive user. That is, Windows services can run while no one is logged on to the system. An example of a Windows service is a device driver or an application that performs background tasks based on a timer. Console applications: Console applications are executables that are run from the Windows command prompt. Functionally, console applications are similar to Windows Forms applications; the difference is that console applications don’t have a Windows Forms user interface and are text oriented. Hosted applications: Last but not least, you can build hosted applications with the .NET Framework. Hosted applications are applications that allow the hosting of managed code inside an application. Hosted applications provide the facility for you to allow your customers (clients) to extend your application. Hosted applications are built with something called Visual Studio Tools for Applications (VSTA). Microsoft also has a variation of VSTA for the Microsoft Office suite called Visual Studio Tools for the Microsoft Office System (VSTO). The idea behind VSTO is to leverage the power of Office, Visual Studio, and managed code to build more feature-rich applications. Historically, VSTO came before VSTA; Microsoft extended the idea in VSTA to allow third parties to benefit from managed code extensibility in its own products. That’s the quick, five-minute tour. We’ll now cover these types of applications individually so you can better understand them and the components they contain.
Windows Forms (Smart Client) Applications Windows Forms applications are desktop applications that have rich user interfaces. For example, the Visual Studio integrated development environment (IDE) is an example of a Windows Forms application.1 Windows Forms applications are built using the .NET Framework class libraries and have the following features:
1. Visual Studio 2005 was not actually written using Windows Forms, although part of it is managed code. The user interface is an example of a Windows Forms application.
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
• They possess a dynamic user interface with rich controls (for example, DataGrids). • Users generally have the ability to do sophisticated actions quickly (for example, dragging and dropping). • The application is installed on desktops and thus uses desktop resources. For example, the application can use the printer, the hard drive, and so on. The application can also communicate with running applications on the machine or spawn new processes and threads. Recently, Microsoft decided to label Windows Forms applications as smart clients (see Figure 1-1).
Figure 1-1. Features of a thick2 client, a thin client, and a smart client
Smart client applications are Windows Forms applications with several additional features to those listed previously. For instance, a smart client has the following features: • It supports offline capabilities. That is, the application doesn’t require a network connection and is intelligent about detecting network connectivity automatically. So, for example, if you run a smart client application on your laptop and decide to go talk to a client in an area where you can’t access your network (or the Internet), the smart client will still work even though your database is not accessible. • It is easy to deploy and update.
2. Thick client, rich client, and fat client are synonyms.
3
4
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
In Visual Studio 2005, you create Windows Forms/smart client applications by choosing Windows Application in the New Project dialog box, under Visual C# or Visual Basic (see Figure 1-2).
Figure 1-2. New Windows Forms application in Visual Studio 2005
Windows Forms applications comprise an executable, zero or more dependent assemblies, resource files,3 and an application configuration file. The executable has an .exe extension, and the dependent assemblies typically have a .dll extension. In .NET, these DLLs are called assemblies. The dependent assemblies are generally placed in a folder named bin or directly next to the executable.4 Often the application will also use shared
3. You can also embed resource files, such as images, data files, and so on, within assemblies. 4. Strictly speaking, putting dependent assemblies within the bin directory is a Visual Studio convention. The common language runtime (CLR) assembly loader uses a concept known as probing to locate assemblies. The bin directory happens to be one of the directories that is “probed” when the CLR looks to load an assembly. For more details about this, see http://msdn.microsoft.com/library/ default.asp?url=/library/en-us/cpguide/html/cpconassemblies.asp.
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
assemblies from the global assembly cache (GAC). The GAC contains assemblies that are shared among the applications installed on the machine. You can see the contents of the GAC by going to %windir%\assembly. A deployment of a Windows Forms application looks like Figure 1-3.
Figure 1-3. Typical Windows Forms deployment
You can deploy Windows Forms applications using ClickOnce.5 You can use ClickOnce to deploy a rich client application using a Web-based deployment model. That is, you can deploy Windows Forms applications over the Web. Other deployment options exist, such as Windows Installer (MSI), but ClickOnce is the new recommended method of deploying Windows Forms applications. Visual Studio 2005 has built-in support for deploying Windows Forms applications. Figure 1-4 shows the Publish dialog box used to configure the deployment of a Windows Forms application. We will talk about this in great detail in later chapters.
5. ClickOnce is new deployment technology built into the .NET runtime 1.0.
5
6
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
Figure 1-4. The deployment of a smart client application using Visual Studio 2005
Web Applications A Web application is an application that is targeted to render in a browser. With the .NET Framework, you build Web applications using ASP.NET. To build a new Web application using Visual Studio 2005, you choose File ➤ New ➤ Web Site. Figure 1-5 shows the New Web Site dialog box in Visual Studio 2005. An ASP.NET application consists of dynamic pages, static pages, configuration files, resources, and dependent assemblies. From a deployment perspective, it is important to know that a Web application’s dependent assemblies are located in a folder named bin.6 Moreover, the configuration of a Web application is stored in a file called web.config. The web.config file is an Extensible Markup Language (XML) file. Application authors generally put environment-specific settings (for example, a database connection string) in this file. Therefore, during deployment, the file will likely need to be modified to reflect the environment in which the application is being deployed. In the past, this task was either done by hand or done by an automated script. With Visual Studio 2005, you can use a Web-based administration console to modify the web.config file of an application (see Figure 1-6).
6. ASP.NET requires Web applications to have a bin folder; with Windows Forms applications, the bin folder is just one place where the probing process looks for assemblies.
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
Figure 1-5. New Web Site dialog box in Visual Studio 2005
Figure 1-6. Web Site Administration Tool console
7
8
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
You can access the administration console from the Website ➤ ASP.NET Configuration menu item in Visual Studio 2005. As shown in Figure 1-6, the administration console is a Webbased tool. From the URL, you can conclude that when Visual Studio was installed, it created an application called asp.netwebadminfiles whose default.aspx file takes the path to a Web application. With this path, the application knows which application’s configuration file to display in the administration console. Note that you can also get to the administration console from Visual Studio 2005 by clicking the ASP.NET Configuration button in the Solution Explorer (see Figure 1-7).
Figure 1-7. ASP.NET configuration via the Solution Explorer in Visual Studio 2005 A typical deployment of a Web application looks like Figure 1-8.
Figure 1-8. A typical deployment of a Web application
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
Web applications are deployed simply by copying files to the Web server.7 Visual Studio 2005 has a built-in Web deployment tool that helps with this (see Figure 1-9). You can access this tool via Web Site ➤ Copy Web Site menu item.
Figure 1-9. Web site deployment tool in Visual Studio 2005
With the deployment tool in Visual Studio 2005, you can deploy your Web applications to a Web server, to a File Transfer Protocol (FTP) site, or to a folder somewhere.
Web Services Web services in .NET have an .asmx file extension. The .asmx file provides the means for clients to call Web services over Hypertext Transfer Protocol (HTTP). The actual Web service implementation is embedded within an assembly. On a Windows platform, Web services are typically hosted under Internet Information Services (IIS).8 Therefore, from a deployment perspective, deploying Web services is no different than deploying Web applications. A Web service, in fact, can reside by itself under its own Web application, or it can reside under a Web application that has the usual ASPX and HTML files. Because Web services are packaged as part of a Web application, Web services have a web.config file that they use for storing and retrieving application configuration.
7. This is commonly called xcopy deployment. 8. You can also host Web services outside of IIS. For example, you can host Web services outside of IIS using Web Services Enhancements (WSE).
9
10
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
Smart Device Applications Smart device applications run on smart devices. Visual Studio 2005 has project templates that target three types of smart devices: Pocket PC 2003, Smartphone 2003, and Windows CE 5.0. The Pocket PC and Smartphone projects target these specific devices; the Windows CE project type does not target any specific device. (In other words, it does not reference any device-specific functionality.) Figure 1-10 shows the New Project dialog box for smart device applications. The dialog box allows you to create graphical applications, console applications, and support assemblies that all target smart devices.
Figure 1-10. New smart device application in Visual Studio 2005
Smart device applications are built on top of the .NET Compact Framework (.NET CF). The .NET CF is a subset of the .NET Framework. This means the .NET CF doesn’t have all the functionality that is available in the .NET Framework. You can build Web applications, console applications, Windows Forms applications, and so on, that target devices that are not smart devices (that is, desktops). Similarly, you can build a Web application or a Windows Forms application that targets smart devices. Therefore, deploying Web applications that target smart devices is no different because Web pages are still deployed to an actual server and rendered to the smart device. Note that you cannot use a smart device as a Web server, however. With Windows Forms and console applications, you have to install the applications on the smart device. This turns out to be different from what you do for desktop applications. That is, you use ClickOnce to deploy Windows Forms applications to desktops. You don’t, however, have this luxury to deploy to smart devices. Instead, you have to package these applications using CAB files.
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
Windows Services Windows services are executables that run in the background and have no user interface. You can create a Windows Service using Visual Studio 2005 by selecting Windows Service in the New Project dialog box (see Figure 1-11). Windows services are supported on Windows NT, Windows 2000, Windows XP, Windows Server 2003, and future versions of the operating system. These versions of Windows are multiuser systems. This means multiple users can be logged on to the system simultaneously. In addition, generally there is no one logged on to the main console (the server itself). Therefore, having a UI for these services is a bit useless.
■Note You can manage and configure Windows services through a console called Service Control Manager (SCM).9
Figure 1-11. New Windows service project and service configuration
Having said that, you can still have a service that has a UI. For almost all cases, Windows services don’t have UIs, but Windows still allows you to have one if you need one. Services that have a UI need a special flag enabled. You can set the special flag by right-clicking the service from the Services list in the Microsoft Management Console (MMC) and choosing Properties ➤ Log On tab (see Figure 1-12). Then check the Allow Service to Interact with Desktop box.
9. Refer to http://www.microsoft.com/technet/prodtechnol/windows2000serv/howto/mmcsteps.mspx for more details.
11
12
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
Figure 1-12. Configuring a Windows service to interact with the desktop10 Windows services comprise an executable, zero or more dependent assemblies, resources, and a configuration file. The executable contains service-level event methods (for example, OnStart), which are fired when a service is started, stopped, and so on, through the SCM (see Figure 1-13).11
Figure 1-13. Start-up configuration of a Windows service12
10. This feature is likely to be dropped in future versions of Windows. 11. For a complete list of events, see http://msdn.microsoft.com/library/default.asp?url=/library/ en-us/vbcon/html/vbconserviceapplicationprogrammingarchitecture.asp. 12. You can view the services on Windows XP by right-clicking My Computer and then selecting Manage. From there, choose Services under Services and Applications.
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
Console Applications Console applications are text-oriented applications that run from the Windows command prompt. You can create a console application by selecting Console Application in the New Project dialog box (see Figure 1-14).
Figure 1-14. New console applications in the New Project dialog box
Similar to Windows Forms applications, console applications comprise an executable, dependent assemblies (DLLs), resource files, and an application configuration file. The executable generally has zero or more dependent assemblies. The dependent assemblies are placed in a folder named bin, which is directly next to the application’s executable. The application configuration file is placed next to the application executable and uses the following naming convention: executableAssemblyName.exe.config. Note that this configuration file generally contains environment-specific settings, among other things. Therefore, this configuration file is something you have to be aware of and know how to modify.
Hosted Applications Hosted applications “host” a scripting engine within the application to provide an extensibility feature. In effect, by hosting a scripting engine, they provide a means for customers to extend the functionality of the application to better meet their needs. In the past, organizations used Windows Script or Visual Basic for Applications (VBA) as a scripting engine. With .NET, you use VSTA instead. VSTA is an improvement over the other technologies because it relies on, and benefits from, the use of managed code. Hosted applications are no different from the other application types. There is nothing more special about how you deploy an application just because it’s hosting a scripting engine.
13
14
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
Application Architectures It is important for a build and deployment engineer to have a foundational understanding of some of the common application architectures in order to effectively do their job. We will now discuss a few of the common application architectures in use today. Specifically, we will discuss the client-server, n-tier, and service-oriented architectures. We’ll start with client-server.
Client-Server Architecture Mainframe architecture was popular in the late 70s and most of the 80s. With mainframes, users sat in front of a terminal, and as they typed, keystrokes were sent to the host for processing. This architecture had some limitations: it didn’t support GUIs, and users couldn’t access multiple databases from geographically remote places. These limitations popularized the client-server architecture. Figure 1-15 shows that within a client-server architecture, applications are divided into three layers: the presentation layer, the business logic layer (BLL), and the data access layer (DAL). Each layer has specific responsibilities. The presentation layer is responsible for managing the user interface interaction with the user, the business logic layer provides business services, and the data layer handles storing and retrieving data. The presentation layer usually resides on the client, and the business logic layer and data layer sit on the server(s). This, however, is not always true. To differentiate this, you need to understand the differences between thin, thick, and smart clients.
Figure 1-15. Layers within a client-server architecture
Thin Client An ASP.NET Web application is an example of a thin client. A thin client application renders the view to the client, and almost all the processing takes place on the server(s). In the context of a client-server architecture, the presentation is rendered to the client (for example, Hypertext Markup Language [HTML] to a browser), and both the business layer and the data layer are distributed across one or more servers. Thin clients are always rendered in the browser and are easy to develop, update, and maintain. They do, however, have several disadvantages: subpar user experiences, compelling security restrictions, and a network connection requirement (in other words, no offline capability). This type of application, however, has been a popular choice in the past decade because it is easy to deploy and can reach users at a global level. Although the disadvantages seem to outweigh the advantages here, quite a bit of improvements have been made to thin client technologies over the past
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
few years. Specifically, the release of .NET introduced ASP.NET, and future improvements are on the way (such as Atlas13).
Thick Client A thick client is the opposite of a thin client. Sometimes this type of application houses all three layers on the client desktop. Thick clients are dynamic and offer users a rich experience. The disadvantages to this type of application include more difficult deployments and poor maintenance and versioning options. Note that the difficulties in deployment and updates led technology decision makers to choose thin clients in the past decade. The tables are turning, however. With the release of Visual Studio 2005, you have a technology that allows you to deploy rich client applications using a thin client deployment model (ClickOnce).
Smart Client Smart clients offer the benefits of both a thin client and a thick client (refer to Figure 1-1). Essentially, if you take all of the advantages of having a thin client and combine them with the benefits of a thick client while throwing away their disadvantages, you end up with a smart client.
N-Tier Architecture In the previous discussion, we talked about the three layers of an application. To reiterate, an application has a presentation layer, a business logic layer, and a data access layer (refer to Figure 1-15). Generally, these layers are separate and run on different machines. For example, you usually see the presentation on a Web server and the business logic and data access layers on an application server. But you sometimes have applications where all three layers are bundled together. When this is the case, you have a one-tier architecture. Similarly, when the presentation is separate from the business logic and data access layers, you have a two-tier architecture. When each of the layers lies on different machines, you have a three-tier architecture (see Figure 1-16).
Figure 1-16. Three-tier architecture
For scalability, performance, and maintainability reasons, an application is broken up into layers. This allows what’s known as n-tier architecture. With n-tier architecture, you can
13. Find out more about the Atlas project at http://www.asp.net/default.aspx?tabindex=9&tabid=47.
15
16
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
have the business logic and data access logic running on many machines, as shown in Figure 1-17. In fact, all three layers can be spread across multiple machines. It’s not uncommon to use Web farms, load balancers, and database clusters to achieve optimal performance.
Figure 1-17. N-tier architecture
Service-Oriented Architecture Service-oriented architecture (SOA) has become a buzzword of late. Although the concepts behind SOA have been around for more than a decade now, SOA has gained extreme popularity lately because of Web services. The fundamental idea behind SOA is that organizations have a host of services that they provide, and we should try to align Web services to these real-world services.14 Doing so will then mitigate the risk when changes are required. Moreover, the Web services that align to the real-world services are built upon XML-based standards, which means you have cross-platform interoperability. Figure 1-18 shows a typical SOA.
14. Web services should implement the “real-world” services provided by the organization.
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
Figure 1-18. A service-oriented architecture
Strategies for Deploying the .NET Framework Applications built with the .NET Framework require the .NET redistributable package (the .NET runtime) in order to run. As it stands now, the .NET runtime is not distributed with the supported versions of Windows operating systems. This means you are responsible for getting the .NET runtime to your clients. Depending on your deployment method, you either package the .NET redistributable with your deployment or require that users have the .NET runtime prior to running your installer. If you are going to require that your clients already have the.NET runtime installed, you can have them download it from http://msdn.microsoft.com/netframework/downloads. The download page provides a link to download the .NET redistributable software development kit (SDK) along with the service packs. In an enterprise environment, you also have other options. For example, large organizations often use Microsoft Systems Management Server (SMS) to handle the automatic distribution of the .NET runtime (among others deployments).
Where Do You Need the .NET Runtime? We started this chapter by talking about the types of applications you can expect to deploy and the architectures of these applications. We talked about the layered client-server application
17
18
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
and how these layers are generally distributed over several machines. Now we will discuss where you need to install the .NET runtime. We’ll explain the typical deployments of a thin client and a smart client, since these are the types of applications you can expect to see.
Typical Deployment of a Smart Client Figure 1-19 shows a typical three-tier deployment of a smart client and where the .NET runtime is required.
Figure 1-19. Typical smart client and .NET runtime requirements
With a traditional three-tier deployment of a smart client, the presentation is implemented using Windows Forms. This requires that the .NET runtime is installed wherever the presentation layer is going to run, which is a user’s desktop. The business logic usually resides on an application server and can be implemented using COM+ components. Thus, the machine where the business logic is going to run requires the .NET runtime. The data access layer can be on the same machine where the business logic is deployed; however, it’s not uncommon to see the data access layer on a separate machine. The data access layer is implemented using ADO.NET, so you need to have the .NET runtime installed on this machine as well.
Typical Deployment of a Thin Client Figure 1-20 shows a typical deployment of a thin client application.
Figure 1-20. Typical deployment of a thin client
CHAPTER 1 ■ DEPLOYMENT PREREQUISITES
With a thin client, the presentation layer serves HTML to clients over HTTP. The presentation is usually implemented using ASP.NET. This means you need to have the .NET runtime on the Web server. What about the client’s browser that receives HTML? Since the client is receiving HTML, it does not require the .NET runtime15 (which is a big advantage of building thin clients). The business logic and data access layers both require the .NET runtime if they are implemented using managed code.
Summary In this chapter, we talked about the various types of applications and application architectures. It is important for build and deployment engineers to be familiar with what they are going to be building and deploying. This knowledge comes in handy after you do a build or a deployment and have to verify that it was successful. We also talked about distributing the .NET runtime. In the next chapter, we will start the multichapter coverage of Microsoft’s new build engine (MSBuild).
15. You can host Windows Forms components within a browser, which requires the .NET runtime on the client.
19
CHAPTER
2
■■■
The Unified Build Engine: MSBuild S
oftware systems have moved from stand-alone applications installed on single machines to large, distributed applications hosted over a network of machines. To create executables of stand-alone applications, you opened a command prompt and executed a few commands to convert the source code into executables. To deploy these applications, you took the generated executables and stored them on floppy disks and “sneakernetted” the application to the clients. The distributed systems of today are considerably more complex; they are much larger and so are their user bases. To build and deploy these systems, you must use predefined processes, along with automated build and deployment tools, to ensure reliability and repeatability. For example, most organizations define what are known as pipelines, and applications are built and deployed automatically to a pipeline for testing and verification. An application starts in an integration environment, then moves to a staging environment, and finally moves to production. At each point, tests are automatically executed in an attempt to ensure quality. At the heart of a build and deployment process is a build and release tool. That is, one tool is responsible for getting, building, and then deploying these applications automatically. Moreover, it is the same tool that is used to migrate the applications from one environment (for example, staging) to another (for example, production). Just as software development tools have evolved, so have the tools that are used to build and deploy them. Why do you need build tools? Why aren’t scripts or batch files sufficient? Well, build tools are a necessary component of application development now because the steps for building software have increased in complexity. Previously, builds required simpler steps, such as copying and moving files, in order to perform the build. Now many applications are using third-party libraries and require more complex tasks, such as file signing and incremental building (more on this in Chapter 4). This increase in complexity has given rise to the need for build-specific tools. In other words, people started by building their software, somewhat manually, and realized they needed a repeatable process. Tools such as Make, NMake, Ant/NAnt, and Jam (among others) can create a repeatable process. In this chapter, we will discuss MSBuild, but we will also highlight some of the most popular build tools and build systems used in the recent past. Note that a build tool is a component or application whose sole responsibility is to take source code and produce binaries (for example,
21
22
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
machine code, Java bytecode, Microsoft Intermediate Language [MSIL], and so on). A build system is a collection of build-related tools that together offer facilities to build, deploy, configure, and test solutions. Build tools and build systems are often packaged as part of popular software development systems.
Introducing Build Tools and Systems In the following sections, we will describe some of the popular build tools and build systems.
Make-Style Build Tools Several build tools are extensions of the original BSD Make. The most popular of the extensions include GNU Make, NMake, OPus Make, Jam, and Cook. With all of these tools, you place a file, usually called a makefile, near the source code that describes what needs to be built. To do a build, users usually enter make in a Unix shell or Windows Command window.
GNU Make GNU Make is probably the most popular build tool on the Unix platform. GNU Make obtained its popularity because of the vast number of extensions it made to BSD Make. GNU Make is a part of the GNU toolset and, thus, is still alive and supported. This build tool is distributed under the GNU open source license.
NMake NMake was originally developed by AT&T Laboratories as an open source project. Recently it has been extended by Lucent Technologies and is packaged as part of a commercial product called Lucent nmake Product Builder. Microsoft also has a version it calls Microsoft Program Maintenance Utility (NMAKE.EXE). The versions produced by AT&T and Lucent are compatible with the original BSD Make; the version produced by Microsoft is not.
OPus Make OPus Make was a popular build tool in the 90s. This tool became popular for its multiplatform support and rather lengthy list of features, including support for logical operators in conditional expressions, regular expression substitutions, a rich set of directives, and more.
Jam and Cook GNU Make, NMake, and OPus Make were all extensions of Make but use the same style used by the original BSD Make. For example, they all relied on the makefile and added features (such as macros). Jam and Cook are variants of Make that don’t rely on the makefile but use another variation of a text file to define what needs to be built. The interesting feature of these two products is that they support parallel builds while avoiding recursion.
GBS The GNU Build System (GBS) is a suite of build tools that, together, provide facilities to build, configure, and release software systems. GBS is popular in the open source community because it provides a feature that places an abstraction layer over the build platform. GBS is mostly used within the open source community.
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
CMake and QMake Cross Platform Make (CMake) and Qtopia Build System (QMake) are build systems, not build tools, that decided to piggyback on the popularity of Make. These build systems have a lot of similarities in that they both market their portability features and support various build tools. CMake offers a GUI and a command-line interface; QMake supports only a command-line interface.
Ant/NAnt The build tools described in the previous sections all use a build description file (for example, a makefile). Most rely on either cryptic commands or full-blown programming languages to describe what needs to be built. The problem with describing builds using commands and/or languages is obviously that the builds are difficult to create and maintain. Ant is a cross-platform build tool popular in the Java community. Ant discarded the idea of using commands or programming languages to describe builds and instead uses XML-based configuration files. Build steps are described using something called tasks, which are implemented with the Java programming language (rather than by writing scripts) and are grouped under a target. This is an example Ant file: <project> <mkdir dir="build/classes"/> <javac srcdir="src" destdir="build/classes"/> <mkdir dir="build/jar"/> <jar destfile="build/jar/HelloWorld.jar" basedir="build/classes"> <manifest> <java jar="build/jar/HelloWorld.jar" fork="true"/> To perform a build using Ant, you place a file named build.xml next to your source tree and enter ant at the command prompt. Ant is command-line driven; however, several popular extensions offer GUIs for Ant. As far as NAnt goes, NAnt is a port of Ant to the .NET platform. NAnt maintains the same concepts of Ant. That is, NAnt also uses an XML-based build file and defines features such as targets, tasks, and so on. We’ll now begin discussing the next-generation build tool: MSBuild.
23
24
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
Introducing MSBuild MSBuild is Microsoft’s solution to the automated build problem. MSBuild allows you to perform all the necessary steps to properly build your .NET applications. MSBuild provides this functionality transparently. In prior versions of Visual Studio, the build process was, for the most part, hidden to the user. You could, for example, supplement your build with pre- and post-events, but you could not change how the build occurred. In Visual Studio 2005, you have this feature! Visual Studio 2005 uses MSBuild to build your solutions. Microsoft has exposed and defined this build process as part of the MSBuild schema. Visual Studio uses your project file as input to MSBuild. In this example, we will show how to create a simple project file using Visual Studio. You can use this file to understand some of the key elements of MSBuild. It will also serve as a point of extension to explore some of the other features of MSBuild. Although MSBuild supports many application types, we will show how to create a simple Windows Forms application. To create this simple project, follow these steps: 1. Start Visual Studio, and create a new C# Windows application named MSBuild1. 2. Accept the defaults, and click OK. 3. In the Form Designer, drag and drop a new label onto the form. 4. Set the text of the label to MSBuild demo. The form should look like Figure 2-1.
Figure 2-1. Sample Windows application Open the project file (MSBuild1.csproj) in your favorite XML or text editor. You’ll notice that the root element is the Project element. Beneath this you’ll see three element types in this file: • PropertyGroup • ItemGroup • Import
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
A few other elements could be present at this level, but we will discuss those elements as you get to them. You’ll see an element that is commented out as well: the Target element. We will discuss this important piece of the MSBuild file later in this chapter. The PropertyGroup element is a container for defined properties. Similarly, the ItemGroup element is a container for defined items. The Import tag allows you to import other MSBuild files into the current project. We will examine how this will affect your files later in this chapter. An MSBuild file has four main elements: properties, items, targets, and tasks. A property defines a value associated with a name. Simply put, it is a key/value pair. In this project file, you’ll find many properties defined: true full false bin\Debug\ You’ll find these defined under the tag. You can change these values directly from Visual Studio by using the Configuration drop-down list. Items are another crucial part of the MSBuild project file. When performing a build, many steps must reference a file or a set of files. In MSBuild this is usually accomplished through items. An item is a named reference to a file or to many files. These items contain associated metadata, such as the full path or filename. Throughout the discussion of MSBuild, we will discuss items in more depth. A target is a container for related tasks that will be executed sequentially. Besides containing tasks, a target can be given a set of dependent targets and a list of inputs and outputs. Incremental building, which is discussed in more detail in Chapter 4, is the process of skipping unnecessary steps. This is driven completely by the associated input and output files for a target. When you invoke MSBuild, you must specify a target that is to be executed; after that, MSBuild will perform a dependency analysis to determine exactly what other targets need to be executed as well. A task is a unit of work in MSBuild. For example, Copy or LocateRequiredAssemblies could be defined as a task. In this sample, you will not find any tasks defined. This is because this sample utilizes predefined tasks to complete the build. (We will cover the predefined task later in this chapter in the “Predefined Tasks” section.) Tasks must be located within a Target element. A Target element consists of a group of related tasks that are executed sequentially. Possible targets are PrepareForDeployment and CopyToServers. As mentioned, this project uses predefined tasks to accomplish the build. You may be wondering where these tasks are actually defined. To answer that question, scroll toward the bottom of the project file, and you’ll find the following declaration: . This import statement is using a property, MSBuildBinPath, to specify where to find the Microsoft.CSharp.targets file, which contains many predefined tasks. The Microsoft.CSharp.targets file is located in the %windir%\Microsoft.NET\Framework\ v2.0.50727\ directory. By using the $() syntax, the property MSBuildBinPath is evaluated, and its value replaces the reference. This property is a reserved property whose value is the location of msbuild.exe. A few other reserved properties exist, which we will discuss in the next section.
25
26
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
Properties As stated, a property is a simple key/value pair. Let’s examine another property definition from the MSBuild.csproj file: MSBuild1. This property is defined in the first PropertyGroup element. If you needed to reference this property somewhere else in the MSBuild file, you would simply use the $() notation. For instance, you would use $(RootNamespace). Refer to the property declaration again: . Notice the Condition attribute; every MSBuild element has an optional Condition attribute. If this condition evaluates to true, then the element is processed; otherwise, it is ignored. Table 2-1 summarizes the basic condition syntax. Table 2-1. Property Conditions
Symbol
Description
==
Checks for equality; returns true if both have the same value.
!=
Checks for inequality; returns true if both don’t have the same value.
Exists
Checks for existence of a file. You provide the file path as an argument, such as in Exists(MSBuildDemo.txt). This will return true if MSBuildDemo.txt is present.
!Exists
Checks for the nonexistence of a file. You use this condition in a similar manner as the Exists condition.
The Debug property will not be executed unless the Configuration property has not already been set. In the next section, you’ll see how you can use these properties in tasks that you create. You may find a number of reserved properties helpful in your MSBuild project. Table 2-2 lists their names, their descriptions, and the values that are returned for the project you will start shortly. In the “Targets” section, you will create a target to print the values for these properties. Table 2-2. Reserved Properties
Name
Description
Example
MSBuildBinPath
Full path of the .NET Framework MSBuild bin directory.
%windir%\Microsoft.NET\ Framework\v2.0.50727
MSBuildExtensionsPath
Full path to the MSBuild folder located in the Program Files directory. This is a nice location to keep other MSBuild files that the current file references.
C:\Program Files\MSBuild
MSBuildProjectDefaultTargets
The value for the DefaultTargets attribute that is in the Project element.
Build
MSBuildProjectDirectory
Full path to the location of project file.
C:\MSBuild\MSBuild1\MSBuild1
MSBuildProjectExtension
Extension of the project filename, including the initial dot.
.csproj
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
Name
Description
Example
MSBuildProjectFile
Filename of the project file, including extension.
MSBuild1.csproj
MSBuildProjectFullPath
Full path to the project file, including the filename.
C:\MSBuild\MSBuild1\ MSBuild1\MSBuild1.csproj
MSBuildProjectName
Name of the MSBuild project file, excluding the file extension.
MSBuild1
Targets As mentioned, targets are containers for related tasks that will be executed sequentially. Some example targets are Build, Deploy, and SetupEnvironment. Let’s examine the parts of a target: <Message Text="SampleTarget executed, SampleInput: @(SampleInput)" /> <Message Text="DependentTarget executed" /> Each target has a Name attribute that is required to be a nonempty string. This name is how you will refer to the target. Additionally, a target can have inputs; if you declare a target to have inputs, then it must have outputs as well. The purpose of the inputs/outputs is to facilitate incremental builds. That is, if a portion of your build does not need to be reexecuted, then it will not be. In a build process, if you had a Target defined as CopyResources, it may take as an input a list of files containing the location on disk of all external resources. The corresponding output may be the desired location of these resources. When MSBuild encounters this target, it will compare the time stamps of these files to each other. If it is not necessary to reexecute that CopyResources target, then it will be skipped. The DependsOnTarget parameter is a list of targets, separated by semicolons, that are required to be run before this target executes. It is important to note at this time that during a build a target will be executed only once. So, if you had two targets that both depended on a common target, that one target will not be executed twice but only once. Now you will examine how you can get started executing some targets that employ some of the predefined tasks.
Executing and Creating Targets In this section, we will discuss the process involved in executing and creating targets. You can utilize many predefined targets in your builds. (Many predefined tasks also exist; we will wait to cover those in the “Predefined Tasks” section.) To emphasize the detachment of MSBuild from Visual Studio, we will be executing the targets strictly from the command line. The first step you’ll want to perform is to open the previously created project file in your favorite text editor. Then, open the Visual Studio 2005 command prompt from the Start menu. From the
27
28
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
command prompt, navigate to the directory in which your MSBuild1.csproj file is located. (Note that MSBuild is also capable of processing solution files, despite that solutions files are not in MSBuild format.) From there, execute the following command: >msbuild. This will execute the default target on the only project file residing in that directory. Your output should look something like Figure 2-2.
Figure 2-2. Output from msbuild.exe on the default target You specify the default target in the root Project element of the project file. In this case, you have . If you invoke MSBuild on this project file without specifying the target, then the Build project will be executed. This target is a predefined target and is not included in your project file. Actually, no targets are defined in this file. This file has an import statement that imports Microsoft.CSharp.targets, and that file imports Microsoft.Common.targets. These two files define many targets, and these are the predefined targets. We will discuss this in more detail in the “Predefined Targets” section. Now let’s inject the two targets discussed earlier into this project file. You can place them anywhere in the file as long as they are defined as child elements of the Project tag. As a reminder, the two targets are as follows: <Message Text="SampleTarget executed, SampleInput: @(SampleInput) " /> <Message Text="DependentTarget executed" />
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
At this point, save this file as MSBuild1_rev2.csproj so you have a backup of the project file in the same directory. The previous example executed the only project in the directory. If two or more project files exist, then you must specify which one to execute. To do this, you can supply the name of the project to build as a command-line parameter. To build this project, execute the following command: >msbuild MSBuild1_rev2.csproj. Following this, you may see similar output as you did when you built the previous project, or you will see that many targets were skipped. This depends on whether your source files have changed since your last build. Skipping up-to-date targets is called incremental building and is a core aspect of MSBuild; we will discuss this in the “Predefined Targets” section and in more depth in Chapter 4. If you’d like to see it build again, you can invoke >msbuild MSBuild1_rev2.csproj /t:Clean;build. This will clean out the previously built files and then build the project. Notice that the target names are case-insensitive. To execute the target, you simply execute >msbuild MSBuild1_rev2.csproj /t:SampleTarget. Figure 2-3 shows the output.
Figure 2-3. Output from the execution of SampleTarget
Now that you have started specifying some parameters for msbuild.exe, you may be interested in what other options are available. Table 2-3 summarizes those options. Table 2-3. msbuild.exe Command-Line Parameters
Switch
Short
Description
/help
/?
Displays usage for msbuild.exe.
/nologo /version
Inhibits the copyright message when msbuild.exe is executed.
/ver
@file /noautoresponse
Displays the version of msbuild.exe. Allows you to pass command-line parameters to msbuild.exe from the file specified.
/noautorsp
Allows you to specify to not automatically include the msbuild.rsp file. This file can specify commandline arguments for MSBuild. If it is present, it will be consumed, unless you set this flag. If you have long command-line arguments, this is the suggested manner to pass them to MSBuild. Continued
29
30
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
Table 2-3. Continued
Switch
Short
Description
/target:
/t
Specifies which targets should be executed. Targets are declared in a semicolon-separated list.
/property:=
/p
Allows you to set properties for the build. If a property is specified that exists in the project file, then this value will take precedence.
/logger:
Specifies the logger used to capture and records MSBuild events as they occur.
/verbosity:
/v
Sets the type of information MSBuild will output. Possible values include d (detailed), diag (diagnostic), m (minimal), q (quiet), and n (normal).
/consoleloggerparameters <parameters>
/clp
Passes parameters to the console logger for MSBuild.
/noconsolelogger
/noconlog
Turns off logging to the console.
/validate
/val
Validates the MSBuild project file with the MSBuild schema file in use.
/validate:<schema>
/val
Validates the MSBuild project file with the MSBuild schema file specified.
From the output of the previous example in Figure 2-3, did you notice that the dependent target executed first? As mentioned, a target will execute only once during the build process. To demonstrate this, add the following target to your MSBuild project file: <Message Text="DependsAgain has executed"/> Now invoke MSBuild with the following command: C:\MSBuild\MSBuild1\MSBuild1> msbuild MSBuild1_rev2.csproj /t:SampleTarget;DependsAgain. Figure 2-4 shows the output.
Figure 2-4. msbuild.exe output for the target SampleTarget;DependsAgain
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
As you can see, DependentTarget seems to have executed only once, before the execution of SampleTarget. The output presented in Figure 2-4 does not make it obvious that this target was skipped the second time. If you use the command-line parameter /v:d or /v:diag, it will explicitly state that this target was indeed skipped. Previously it was mentioned that a Target will be described to print the values for the reserved properties; the specification for that target is as follows: <Message Text="MSBuildProjectDirectory $(MSBuildProjectDirectory)" /> <Message Text="MSBuildProjectFile $(MSBuildProjectFile)" /> <Message Text="MSBuildProjectExtension $(MSBuildProjectExtension)" /> <Message Text="MSBuildProjectFullPath $(MSBuildProjectFullPath)" /> <Message Text="MSBuildProjectName $(MSBuildProjectName)" /> <Message Text="MSBuildBinPath $(MSBuildBinPath)" /> <Message Text="MSBuildProjectDefaultTargets $(MSBuildProjectDefaultTargets)" /> <Message Text="MSBuildExtensionsPath $(MSBuildExtensionsPath)" />
: ➥ : ➥ : ➥ : ➥ : ➥ : ➥ : ➥ : ➥
After you add this to the project file, you can invoke it with the following command: >msbuild MSBuild1.csproj /t:PrintReservedProperties. Note this was added to the original version of the project file, not the MSBuild1_rev2.csproj file. In Figure 2-5, you can see the values for the reserved properties that are available with MSBuild. Now we will explain some predefined targets.
Figure 2-5. Output for reserved properties
31
32
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
Predefined Targets Previously you saw the predefined Build target execute. You may be wondering what other predefined targets are available and where they are located. Predefined targets are housed in either the Microsoft.CSharp.targets file (if your project is a C# project; otherwise, see the import statement in your project file) or the Microsoft.Common.targets file. The Microsoft.CSharp. targets file is imported into your project with the Import element. That file then imports the Microsoft.Common.targets file. As you examine these predefined targets, keep in mind that you may override them to declare your own behavior. For example, if you wanted to copy resources to another location during the build process, then you could change the Build target. You will examine a target that changes how your C# files are built later in this section. Table 2-4 lists some predefined targets; this is not an exhaustive list because many targets exist simply as support for other targets. Table 2-4. Some Predefined Targets
Name
Description
BeforeBuild
Empty target that will be called before the build process begins. You can use this to augment the build with your own steps. For example, you can use this to clean items or stop running services.
AfterBuild
Empty target that will be called after the build process is complete. You can use this if you want to augment the build process with your own build steps. For example, you can use this to copy output to specific directories or restart a service.
CoreBuild
This is the target that actually builds your project.
BeforeCompile
Empty target that will be called before your project gets compiled. If you need to augment the compile with preprocessing, override this task.
AfterCompile
Empty target that will be called after your project has been compiled. If you need to perform steps after project compilation, you can override this task.
Compile
Target responsible for compiling your project.
CoreCompile
Target that will actually make the call to the underlying compiler for your files.
BeforeRebuild
Empty target that will be executed before Rebuild starts.
AfterRebuild
Empty target that will be executed after Rebuild has completed.
Rebuild
Target that rebuilds your project.
BeforeClean
Empty target that is executed before Clean is performed.
AfterClean
Empty target that is invoked after the Clean target has completed.
Clean
Target that will perform a clean on your project. All intermediate and built files will be removed.
PostBuildEvent
Target that will be specified through Visual Studio to run after a successful/unsuccessful build.
PreBuildEvent
Target that will be created through Visual Studio to run before a build.
Run
Target used to start your application, if it is an executable project.
BeforePublish
Empty target that will be called before Publish has started.
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
Name
Description
AfterPublish
Empty target that will be executed after Publish has completed.
Publish
Use this to replicate the ClickOnce Publish behavior from outside Visual Studio. Note: This is not the target that Visual Studio will invoke when using ClickOnce, but it is the suggested means to replicate the functionality.
PublishOnly
Publish target used by ClickOnce. Note: If you override BuildPublish and AfterPublish, those targets will also be invoked by this target.
SignClickOnceDeployment
Target that will sign the deployment files created by ClickOnce.
BeforeResolveReferences
Empty target that is executed before ResolveReferences is invoked.
AfterResolveReferences
Empty target that is called after the ResolveReferences target has completed.
ResolveReferences
Target that is responsible for resolving the project references.
BeforeResGen
Empty target that is invoked before ResGen is run.
AfterResGen
Empty target that is called after ResGen has completed.
ResGen
Target that will generate resources for your project.
GenerateBootstrapper
Target that will create the bootstrapper setup.exe file.
CreateSatelliteAssemblies
Target that will create all the assemblies for all the cultures your project has defined.
GenerateApplicationManifest
Target that will create an application manifest for your project.
ComputeClickOnceManifestInfo
Target that will gather the information necessary to create a ClickOnce manifest.
GenerateDeploymentManifest
Target that will create a deployment manifest for your project.
Now that you know many of the existing targets, how can you change how these steps are executed? Well, keep in mind that the C# targets, properties, and tasks can be overridden with custom versions. The version defined last will be chosen (more on this later in this section). One method of changing these existing targets is to completely override the entire target. This will likely not be the mechanism you’ll want to use. A better alternative is to create a target you’d like to have performed. Then inject that target in the DependsOnTargets list at the location at which you’d like it to be executed. To clarify this, you will learn how to change the way your projects are built. To change how your project is built, you have a few options. You can create targets BeforeBuild and AfterBuild, and those will be executed at the appropriate time. You could define PreBuildEvent or PostBuildEvent. These options provide a simple and convenient means for customizing the build process, but what if you need more control? Previously we mentioned that you can redefine the build target, but this is not a good idea. We’ll show how a build is performed, and then we will show a better method. From the Microsoft.Common.targets file, the Build target, and its required property, is defined as follows: <BuildDependsOn> BeforeBuild; CoreBuild;
33
34
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
PostBuildEvent; AfterBuild From this excerpt you can see that the Build target itself doesn’t actually do anything; it simply calls other targets to do all the work. Also notice that the DependsOnTargets attribute is a property instead of simply containing the list itself. This is because if you wanted to add a step to the build, you could just override the BuildDependsOn property with your target name injected in there. For example, if you had a target defined as RecoredBuildCompletion that you wanted to execute after your build completed, you would simply insert the following property definition into your project file: <BuildDependsOn> BeforeBuild; CoreBuild; PostBuildEvent; AfterBuild RecordBuildCompletion; Now when your project gets built, MSBuild will ignore the BuildDependsOn property that is defined in the Microsoft.Common.targets file, because this BuildDependsOn property overrides its definition. When MSBuild evaluates properties and items, the last definition provided will be used. So, the placement of these declarations is important; in this case, this declaration must be after the import declaration for Microsoft.Common.targets. Because of this statement, your RecordBuildCompletion target will be executed at the end. For this simple example, this is the same as overriding the AfterBuild target. Continuing with how projects are built, you’ll notice that CoreBuild is a dependent target for the Build target. Let’s examine its definition from the Microsoft.Common.targets file: BuildOnlySettings; PrepareForBuild; PreBuildEvent; UnmanagedUnregistration; ResolveReferences; PrepareResources; ResolveKeySource; Compile; SGen;
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
CreateSatelliteAssemblies; GenerateManifests; PrepareForRun; ObjectRelationalValidator; UnmanagedRegistration; IncrementalClean Here you can see how the build process is broken down. You’ll want to look at the CoreBuildDependsOn property. The items in that list are targets that will be executed, in order, when your project is built. If you need more fine-grained control over the build, typically you’ll create a new target and override the CoreBuildDependsOn property with it included in the list. So let’s do just that; we will show how to create a target, PrintIntermediateAssemblyName, and place this target after the Compile step in the build process. For this target, you’ll also print the value for the CoreBuildDependsOn property. You just need to modify the CoreBuildDependsOn property and define the required target. Add the following to the project file: BuildOnlySettings; PrepareForBuild; PreBuildEvent; UnmanagedUnregistration; ResolveReferences; PrepareResources; ResolveKeySource; Compile; PrintIntermediateAssemblyName; SGen; CreateSatelliteAssemblies; GenerateManifests; PrepareForRun; ObjectRelationalValidator; UnmanagedRegistration; IncrementalClean
35
36
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
<Message Text="Intermediate assembly name: @(IntermediateAssembly)" /> <Message Text="Int assm full path: $(MSBuildProjectDirectory)\@(IntermediateAssembly)"/> <Message Text="---------CoreBuildDependsOn--------------"/> <Message Text="$(CoreBuildDependsOn)"/> <Message Text="-----------------------------------------"/> The property IntermediateAssembly is an output item of the Compile target. You can just print its value to the console using the Message task. Note: when you access items, you always use the @() syntax, and when accessing properties, you use the $() syntax. You’ll find more about this in Chapter 3. Following this, you will invoke the Build target and watch its output. Since the DefaultTarget is Build, you can call MSBuild on this project without specifying a file, and Build will execute. Use this command to invoke it: >msbuild MSBuild1_rev2.csproj /v:detailed /t:Clean;Build. We chose to execute Clean and then Build to make sure that all targets are reexecuted for the build. Figure 2-6 shows the result of this build.
Figure 2-6. Output from customized build
From Figure 2-6 you can see that the task was indeed executed immediately after the Compile target completed. In previous versions of Visual Studio, this was basically impossible, but it took
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
you only a few minutes to complete it! Now that you know how to execute targets, you can move on to creating some new tasks.
Tasks As stated, a task is a unit of work. You’ll want to create generic and simple tasks. If you need to adhere to a flow of execution, such as locate ➤ copy ➤ delete, you’ll want to create each of those as tasks; actually, you will most likely have to write only the locate task because the copy and delete are predefined. The components that will model the flow of execution will be targets. Tasks are usually declared within targets, but it is possible to have them outside as well.
Predefined Tasks Many predefined tasks are available to you out of the box. These tasks are declared in the Microsoft.Common.Tasks file and are contained in the Microsoft.Build.Tasks assembly. Table 2-5 summarizes the predefined tasks that are available. Table 2-5. Predefined Tasks
Name
Description
AL
Assembly linker. Creates one assembly from many other components.
AspNetCompiler
Precompiles ASP.NET files.
AssignCulture
Given a list of files whose filenames contain a culture identifier, creates items that contain this culture as metadata, instead of being embedded in the filename.
Copy
Copies files from one location to another.
CreateItem
Creates a new item. You can also use this to copy an item.
CreateProperty
Creates a property, similarly to CreateItem; you can use this to copy properties.
Csc
Invokes the C# compiler.
Delete
Deletes files.
Error
Raises an MSBuild error.
Exec
Invokes the specified application.
FindUnderPath
Determines whether specified items exist under the specified path.
GenerateApplicationManifest
Creates an application manifest that can include such things as globally unique identifiers (GUIDs), assemblies, and files. Note: You can use this to create a ClickOnce manifest.
GenerateBootstrapper
Creates an application that can download your application and all of its dependencies.
GenerateDeploymentManifest
Creates a ClickOnce manifest.
GenerateResource
Creates resources; this is functionally similar to the resgen.exe application.
GetAssemblyIdentity
Given a list of files that contain assemblies, gets all the IDs of contained assemblies. Continued
37
38
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
Table 2-5. Continued
Name
Description
GetFrameworkPath
Returns the full path of the location where the .NET Framework assemblies are installed.
GetFrameworkSdkPath
Returns the full path of the .NET Framework SDK.
LC
Creates license files for applications.
MakeDir
Creates directories.
Message
Outputs messages.
MSBuild
Invokes a build on another MSBuild project file.
ReadLinesFromFile
Reads lines from a specified file.
RegisterAssembly
Used in a similar way as the Assembly Registration tool (regasm.exe).
RemoveDir
Removes directories and their contents.
RemoveDuplicates
Removes duplicates from the given item collection.
ResGen
Wrapper for the resgen.exe application that can be used to create binary .resource files.
ResolveAssemblyReference
Given a list of assemblies, determines what other assemblies depend on assemblies in that list.
ResolveComReference
Given either a list of library names or a .tlb file, discovers the location on disk.
ResolveKeySource
Resolves the strong name key source.
ResolveNativeReference
Resolves native references.
SGen
Creates an XML serialization for specified assemblies.
SignFile
Given a certificate, signs files.
Touch
Updates the file access/modified date.
UnregisterAssembly
Unregisters the specified assembly. This is the inverse operation of the RegisterAssembly task.
Vbc
Wrapper for the Visual Basic compiler.
VCBuild
Wrapper for the Visual C++ compiler.
Warning
Raises an MSBuild warning.
WriteLinesToFile
Writes lines to text files.
Before you begin to write a task, or a target for that matter, you should always doublecheck to make sure you’re not reinventing the wheel. We will show how to create a simple task: HelloTask.
Creating a Task By this point you probably have a general feeling for what a task is, but you most likely still have many questions regarding them. For example, how can new tasks be implemented? Tasks actually are implemented in code, so whatever you are able to do in code you are able to accomplish in a task. This even includes showing dialog boxes and launching external applications. In the build process, you probably will not want to show dialog boxes, but you have the option if you desire.
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
■Note Before we discuss tasks, we will briefly discuss metadata, which is used in the task samples. Items can have metadata associated with them. Items actually have two classes of metadata: well-known metadata and custom metadata. We will provide more details about metadata in later chapters. An example of the well-known metadata is FullPath; you can get the full path to an item by simply getting the value of this metadata item. To access metadata, you use the %() operator. To get the full path for an item, use the syntax @(ItemName->'%(FullPath)').
In this section, you will learn how to create a simple task, including invoking it from your project file. This simple task will be HelloTask. The HelloTask task will accept an assembly name as its input, prepend HelloTask:, and return it to the target that called it. To do this, your task must be able to accept an input and be able to create an output. Generally, creating a task involves three general steps: 1. Specify the task in your project file. 2. Write the .NET class behind the task. 3. Specify in the project file where the task can be found. Now you will dissect these steps and create your first custom task! You first need to specify the task in your project file. Since you know you will pass in the assembly name, it should depend on the Compile target, and you want to print the result to the console. Insert the following target into the project file: <Message Text="$(HelloTaskString)"/> To invoke this task, execute the following command: >msbuild MSBuild1_rev2.csproj /t: DoHelloTask. If you get some errors, usually they are descriptive, and you may need to modify your project file. If all goes well, your output should look like Figure 2-7.
Figure 2-7. Output from HelloTask execution
From Figure 2-7, you can see that HelloTask successfully completed and printed the path of the intermediate assembly to the console. Notice that this target also caused the Compile target to be executed. This is the expected behavior because it is in the DependsOnTargets list. From this simple example, you can imagine how your projects can use MSBuild to automate and streamline your build and deploy methods. In later chapters, we will discuss in further detail how to use MSBuild.
CHAPTER 2 ■ THE UNIFIED BUILD ENGINE: MSBUILD
Summary In this chapter, we discussed what build tools are and why they are now a required component in the software development life cycle. We also introduced Microsoft’s new build tool, MSBuild. We covered all of the key elements that you need to start customizing your builds using MSBuild. For the next few chapters, we will build on this foundation so you can extend your build process to suit your needs. In the next chapter, for example, we will show MSBuild in action. You will see many examples of using MSBuild to help you comprehend some of the difficult-to-understand features and to provide more of a concrete basis on which to further build.
43
CHAPTER
3
■■■
MSBuild: By Example E
ven if you have experience using an XML task-based build tool, such as Ant or NAnt, MSBuild is significantly different. In fact, MSBuild is different not only in execution but also in syntax. Therefore, to really get a feel for MSBuild, you must get your hands dirty using the tool. As you begin to explore what MSBuild has to offer to your projects, you will naturally seek more knowledge of MSBuild. This chapter will help you get your hands dirty by showing you several examples of how you can use MSBuild. Also, this chapter will present some important techniques for using MSBuild effectively. We will provide a variety of tips, covering topics such as integrating MSBuild into Visual Studio and formatting your output. These samples are set up to be mostly independent. This is because each sample expresses a set of specific ideas, so you will be able to examine and try each concept on its own. After this chapter, you should have a much greater feel for building your applications with MSBuild. Following this chapter, we will continue the coverage of MSBuild by showing how to use some of its more advanced features.
Introducing Well-Known Metadata When using MSBuild, you have two primary ways to pass data to tasks and targets; those are through properties and through items. A property is a key/value pair, and an item is typically a reference to a file. For example, when your project is compiled, the Compile item is evaluated to determine which files should be included. Using items for files over properties has some advantages; one of these advantages is that items can have metadata attached to them. The following is a sample of an item declaration from the Microsoft.Common.targets file: <AppConfigFileDestination Include="$(OutDir)$(TargetFileName).config"/> In the previous declaration, the item AppConfigFileDestination is being defined, and its value is specified in the Include attribute. This attribute is using two properties, OutDir and TargetFileName, to create the name of the config file. An example of its value is bin\debug\ WindowsApplication1.exe.config.
45
46
CHAPTER 3 ■ MSBUILD: BY EXAMPLE
When you specify which files are included in the item declaration, you can include multiple files by separating them with semicolons. Another way to include multiple files is to use expressions that include wildcards. You can use three wildcard elements with MSBuild: ?, *, and **. You can use ? to replace a single character with any character. For example, the include declaration Include="c?r.cs" would include the files car.cs, cbr.cs, ccr.cs, and so on. The * element can replace any location with zero or more characters. To change the previous example, Include="c*r.cs" would include car.cs, caar.cs, cr.cs, colorer.cs, and so on. The ** notation tells MSBuild to search the directories recursively for the pattern. For example, Include="src \**\*.cs" would include all the files under the src directory with .cs extensions. When you include an item in your MSBuild project file, it may seem you are simply adding a text entry, but what you don’t see is what’s happening behind the scenes. When you add an item, you’re adding a rich object to your project, and you get some information for free! For this example, we will use MetaDataEx.csproj. This is a simple Windows Forms application, similar to the one contained in Chapter 2. Table 3-1 describes the metadata that is automatically set when your project file is loaded; this is well-known metadata. Table 3-1. An Item’s Well-Known Metadata
Metadata Name
Description
Identity
A unique value for each item in the item collection.
Filename
Filename for this item, not including the extension.
Extension
File extension for this item.
FullPath
Full path of this item including the filename.
RelativeDir
Path to this item relative to the current working directory.
RootDir
Root directory to which this item belongs.
RecursiveDir
Used for items that were created using wildcards. This would be the directory that replaces the wildcard(s) statements that determine the directory.
Directory
The directory of this item.
AccessedTime
Last time this item was accessed.
CreatedTime
Time the item was created.
ModifiedTime
Time this item was modified.
The following is a target that demonstrates how to use well-known metadata: <MDForm Include="MetaDataFrm.cs"> Sayed Ibrahim Hashimi <Email>
[email protected] CHAPTER 3 ■ MSBUILD: BY EXAMPLE
<MDFormOther Include="..\..\**\MSBuild1\*.cs"> Sayed Y. Hashimi <Email>
[email protected] <Message Text="Normal: @(MDForm)" /> <Message Text="FullPath: @(MDForm->'%(FullPath)') " /> <Message Text="RootDir: @(MDForm->'%(RootDir)')" /> <Message Text="Filename: @(MDForm->'%(Filename)')" /> <Message Text="Extension: @(MDForm->'%(Extension)')" /> <Message Text="RelativeDir: @(MDForm->'%(RelativeDir)')" /> <Message Text="Directory: @(MDForm->'%(Directory)')" /> <Message Text="RecusriveDir: @(MDForm->'%(RecursiveDir)')" /> <Message Text="Identity: @(MDForm->'%(Identity)')" /> <Message Text="ModifiedTime: @(MDForm->'%(ModifiedTime)')" /> <Message Text="CreatedTime: @(MDForm->'%(CreatedTime)')" /> <Message Text="AccessedTime: @(MDForm->'%(AccessedTime)')" /> <Message Text="%0D%0A;--------------"/> <Message Text="Recursive dir [MDFormOther]: "/> <Message Text="%09@(MDFormOther->'%(Filename)➥ %09%(RecursiveDir)', '%0D%0A%09;')"/> <Message Text="%0D%0A;Relative dir [MDFormOther]: "/> <Message Text="%09;@(MDFormOther->'%(Filename)➥ %09;%(RelativeDir)', '%0D%0A%09;')"/> In this snippet from the MetaDataEx project file, an ItemGroup contains the item declarations upon which this target will act. This ItemGroup defines two items. One of these items, MDForm, includes only a single file and is explicitly defined. This item will provide the results of many of the metadata queries in this sample. The other item contains many files and has been defined using wildcards. This item will demonstrate how to use the RecursiveDir and RelativeDir metadata values. This file also includes some formatting of the output; for further information regarding formatting, see the “Formatting Your Output” section. To invoke this target on your project, you will invoke MSBuild by executing the following at the command line: >msbuild MetaDataEx.csproj /t:ShowWellKnownMD. Figure 3-1 shows the output from this target.
47
48
CHAPTER 3 ■ MSBUILD: BY EXAMPLE
Figure 3-1. Well-known metadata output In Figure 3-1 you can see that MSBuild was able to resolve the items to the actual files on disk. If you are creating a new task that acts upon a file, you most likely will want to pass the task the FullPath value of the item. This will ensure that your task is dealing with the same file as your MSBuild project file. For example, if you want to copy IntermediateAssembly to another location, then you can use a declaration similar to the following one: This will copy the IntermediateAssembly to the Destination location and preserve the filename and extension. It is a best practice to use the FullPath for items as inputs to the tasks to ensure that no other file can be used in its place. When you are creating your MSBuild targets and tasks, it is helpful to remember what metadata is available to you out of the box and to remember how you can use it effectively.
Formatting Your Output As you use MSBuild to build your projects, you may want to format your output for increased readability or for other reasons. MSBuild uses the % character to show the beginning of an escaped character. The % character is followed by the ASCII character code for the desired character. You can find a complete reference for these codes in the MSDN Help documentation. For example, if you would like to place a carriage return line feed (\r\n) in your text, use the %0D%0A code. For example, consider the following FormatNewLine target, which is in the MSBuildEx.csproj file: <Message Text="FirstLine%0D%0ASecondLine" />
CHAPTER 3 ■ MSBUILD: BY EXAMPLE
To execute this target, you call MSBuild with the following at the command line: >msbuild MetaDataEx.csproj /t:FormatNewLine. Figure 3-2 shows the output from this target.
Figure 3-2. MSBuild-formatted output
From Figure 3-2 you can see that the new line was successfully inserted into the output for this text. Table 3-2 lists some ASCII character codes that you may find useful when creating your project files. Table 3-2. Useful ASCII Character Code Escape Values
Character
ASCII Escape Value
Carriage return
%0D
Line feed
%0A
New line
%0D%0A
Tab
%09
Space
%20
Quotes (")
%22
Apostrophe (')
%27
Ampersand (&)
%26
Percent sign (%)
%25
In some circumstances when you are using a vector value, you may want to change the delimiter when using the Message task. Refer to the following target: <Message Text="MDFormOther files:" /> <Message Text="@(MDFormOther->'%(Filename)', ',')" /> The <Message Text="@(MDFormOther->'%(Filename)')" /> call will print the values for the files included with the default delimiter, which is a semicolon. If you want a different delimiter, you can specify it as an argument. For example, the <Message Text="@(MDFormOther-> '%(Filename)', ',')" /> call changes the delimiter to a comma. You can also use this feature to
49
50
CHAPTER 3 ■ MSBUILD: BY EXAMPLE
format your output! For example, if you want to align all the filenames, you can specify the delimiter to be a new line. Add the following message invocation to the target: <Message Text="@(MDFormOther->'%(Filename)', '%0D%0A')" /> This invocation is specifying that a new line should delimit all the entries that will be included in the list. The target will look like the following snippet now: <Message Text="MDFormOther files:" /> <Message Text="Comma delimiter" /> <Message Text="@(MDFormOther->'%(Filename)', ',')" /> <Message Text="Default delimiter" /> <Message Text="%09@(MDFormOther->'%(Filename)')" /> <Message Text="%0D%0A"/>
CHAPTER 3 ■ MSBUILD: BY EXAMPLE
<Message Text="Align on new lines"/> <Message Text="%09@(MDFormOther->'%(Filename)', '%0D%0A%09;')" /> <Message Text="%0D%0A"/> Figure 3-4 shows the output from executing this new target.
Figure 3-4. Output from message with formatting You can see that the output from this target execution is much more readable than the previous invocations. Now you have successfully made the output much easier to read, but what have you done to the readability of the build file itself? Sifting through all the ASCII values is not only nonintuitive but is distracting. What can you do to avoid this problem? You may have guessed—you can keep these values inside properties. In a few cases, this method doesn’t work as expected, such as when you are placing whitespace-related items inside the properties. But you can get around that. We will skip covering those issues for now, however, in order to examine the other issues first. Refer to the following properties: %40 %25 %22 <SINGLE_QUOTE>%27 %0D %0A <Message Text="@(MDFormOther->'%(Filename)%(Extension)', '
')" />
<Message Text="%09Name:%09%(MDFormOther.Name)"/> <Message Text="%09Email:%09%(MDFormOther.Email)"/> The MDForm item here contains one value inside it, which is MetaDataFrm.cs. Now you will see what happens when you use both methods to access the value for this item and compare the results. The following is a simple target to demonstrate this: <Message Text="%40(MDForm->'% (Filename)'): @(MDForm->'%(Filename)')" /> <Message Text="%25(MDForm.Filename): %(MDForm.Filename)" /> This target will simply print the value for the filename of this item. Note the use of %40, which is the escape code for the at (@) character; similarly, the % character is escaped with %25. Figure 3-12 shows the output from this target.
Figure 3-12. Output from the target VectorScalar1
59
60
CHAPTER 3 ■ MSBUILD: BY EXAMPLE
As you can see, the results of both reference methods are the same! Well, now you will see another target that is similar and see whether there is any difference. The following is the definition of the target and the property that it references: <Message Text="%40(Compile->'% (Filename)'): <Message Text=" "/>
@(Compile->'%(Filename)')"/>
<Message Text="%25(Compile.Filename)%25(Extension):➥ %(Compile.Filename)%(Extension)"/> <Message Text=" "/> <Message Text="%40(Compile->'%25(Filename)')%25(Extension):➥ @(Compile->'%(Filename)')%(Extension)"/> In the first message, you are passing the value @(Compile->'%(Filename)%(Extension)'). The @ character says you are acting upon the list items of the Compile item, and the % character determines which transformations are to be executed. In this case, you have %(Filename)% (Extension). Figure 3-14 shows the output from this message task and for the remainder of the target. We will explain the other items in this target shortly.
Figure 3-14. Output from the VectorScalar3 target
As you can see from Figure 3-14, the extensions of the files, which all happen to be .cs, were appended to the text sent to the Message task. As mentioned, the %(Extension) transformation is within the @ reference. You will examine the behavior when this is not the case in a bit. For now you will proceed to the individual transformations. The next call to the Message task is provided %(Compile.Filename)%(Extension) for the text value. For each item in the Compile list, a message task will be executed as before, but this time the filename followed by the extension is sent to the Message task. Imagine that you iterate over the Compile item list and that inside this loop you build a string with the filename and extension from the current Compile item list. Notice in the %(Extension) transformation, the item name was not specified. This is OK because this information is gathered from the Compile reference. You may not find the last example to be too useful; instead, it further clarifies the distinction between these two methods. In the last call, the Message task is provided with @(Compile->'% (Filename)')%(Extension). Notice the difference between this text and the text from the first invocation of the Message task. The key difference here is that the %(Extension) is outside the @() reference. So in the output, instead of being included in the array output, %(Extension) was simply appended to the output from the array output. Now you should be prepared to use these mechanisms in your MSBuild files without getting tripped up.
CHAPTER 3 ■ MSBUILD: BY EXAMPLE
Using Environment Variables in Your Project In Chapter 2, you were exposed to what properties are and how to use them. Properties are similar to something you may already be familiar with—environment variables. The main difference is that a property is defined within the scope of an MSBuild execution, whereas an environment variable is available throughout the machine. In your builds, you may want to reference these environment variables, and doing so is easy! To use the value of an environment variable, you simply refer to that just as you would if it were a property. Refer to the following target, which prints the value for the Path environment variable: <Message Text="Path: $(Path)"/> To invoke this target at the command line, execute >msbuild MetaDataEx.csproj /t:PrintSystemPath. Figure 3-15 shows the output from this invocation.
Figure 3-15. Output from the PrintSystemPath target
In Figure 3-15 you can see the system path’s value output to the console. As you can see, using a property is just as easy as using a property that is defined in your MSBuild project file. All environment variables are available to be used just as properties are in your MSBuild project files.
Reusing MSBuild Project Elements As you create MSBuild project files, you will want to reuse some of the elements that you defined previously. For instance, let’s say you have created a new target that deploys your application to a set of servers. You will need to reuse this target in more than one project. How can you do this effectively? The most obvious answer is to copy and paste this target into every project file that needs it. This is probably the easiest solution, but it’s the most difficult to maintain as well. Imagine that in your organization you have 30 projects that use this target, and you need to change the location of one of the servers. You’ll have to search for all these project files and update them. Consolidating this functionality in a single place would be much better. With MSBuild you can achieve this. The element that you will need is the Import project element.
63
64
CHAPTER 3 ■ MSBUILD: BY EXAMPLE
So, we’ll now show a simple example of using the Import element. This sample will include the Import1.proj file, which is as follows: ftp.sedodream.com ftp.apress.old.com In this project file you can see that two properties are defined that point to FTP sites. These may be two servers that applications need to be deployed in. The file that uses this, which is named ImportEx1.proj, is as follows: <Message Text="FtpServer1URL: $(FtpServer1URL)"/> <Message Text="FtpServer2URL: $(FtpServer2URL)"/> In this project file you are importing the Import1.proj file with the statement. Typically when you create files that are to be imported into other projects, you’ll want to store them in a special location. This is because you want to avoid copying these files to many different locations. This project file has a single target that simply prints the values for the properties defined in the imported project file. You can execute the PropertyImport1 target as follows: >msbuild ImportEx1.proj /t:PropertyImport1. Figure 3-16 shows the result of this execution.
Figure 3-16. Output from the PropertyImport1 target
In Figure 3-16 you can see that the properties defined in the Import1.proj file were successfully imported into the current project. Before you explore this feature in more detail, let’s examine a related issue. When you are creating MSBuild project files, what happens if there is a conflict of the properties, items, or targets? When two or more MSBuild elements have the same name, the last one defined takes precedence. You can see this in action very clearly in the following simple project file:
CHAPTER 3 ■ MSBUILD: BY EXAMPLE
files.sedodream.com <Message Text="FtpServer1URL: $(FtpServer1URL)"/> ftp.sedodream.com This project file has the property FtpServer1URL being defined twice and a target, PrintFtp, that will print its value. Based on what was stated previously, what value do you expect to be printed? To find out, invoke this target by executing the following at the command line: >msbuild OverrideEx1.proj /t:PrintFtp. Figure 3-17 shows the result of this execution.
Figure 3-17. Output from the PrintFtp target
From the execution of this target you can see that the last defined FtpServer1URL, ftp.sedodream.com, was taken as the value of that property. As you can see, there was no error or warning issued to notify you about this naming conflict. This is an expected behavior of MSBuild files. Similar to the idea of overriding methods in object-oriented programming, it is not an error if you override the value of a previously defined property. Now we’ll show what happens if you override a target. Does it have the same behavior? To determine this, refer to the following simple MSBuild project file: <Email>
[email protected] <Message Text="Emailing: $(Email)"/>
65
66
CHAPTER 3 ■ MSBUILD: BY EXAMPLE
<Message Text="Email sent to : $(Email)"/> The previous target has a property defined, Email, and a target, EmailAdmin, defined twice. According to what we stated previously, the last EmailAdmin target defined should be executed. You can check this by invoking this target with >msbuild OverrideEx2.proj /t:EmailAdmin. Figure 3-18 shows the output from this.
Figure 3-18. Output from the EmailAdmin target
In Figure 3-18 it is clear that indeed the last EmailAdmin target was executed. Before returning to the main point of this section, we’ll mention one more aspect of this. Let’s modify the previous project file a bit; refer to the following: <Email>
[email protected] <Email>Sayed I. Hashimi [$(Email)] <Message Text="Emailing: $(Email)"/> <Message Text="Email sent to : $(Email)"/> This is almost the same project file as you saw previously, but the second definition of the Email property is <Email>Sayed I. Hashimi [$(Email)]. This property looks like it is referencing itself! Think of the Email property as a property defined as a class member, such as in C#. When MSBuild loads this file, it will process the first Email property and set its value. When the second definition is encountered, it gets the current Email property value and adds to it. To execute this target, specify >msbuild OverrideEx3.proj /t:EmailAdmin at the command line. Figure 3-19 shows the result from this execution.
CHAPTER 3 ■ MSBUILD: BY EXAMPLE
Figure 3-19. Output from the EmailAdmin target
As you can see from Figure 3-19, overriding targets has the same behavior as overriding properties. When a target is defined, it will take precedence over any previously defined target with the same name. Now, you are not likely to override a property or target within the same file, so you are probably wondering why we are discussing this. The reason is that you may run into this situation when your project imports other MSBuild project files. Let’s talk about what happens when you import another MSBuild project file into yours. When MSBuild starts processing a project file, it will do this in a node-by-node manner, from the top of your project file to the bottom. When an Import element is encountered, these steps then take place: 1. The working directory is changed to that of the imported project file. 2. Project element attributes are processed. 3. Project element nodes are processed. 4. The working directory returns to importing the previous value. The first step is to change the working directory to that of the imported project file— assuming that it has a different working directory from the current project file, that is. This is necessary to properly handle any relatively defined import statements, or to resolve locations of assemblies when the UsingTask element is inserted, within that project file. This change is not used for item declarations. Let’s move on to the following step. The next thing after that is for the Project element attributes to be processed. Possible attributes are DefaultTargets and InitialTargets. If a value is already assigned to DefaultTargets, then this is ignored; otherwise, it becomes the value of DefaultTargets. If the InitialTargets attribute is present, then that list of targets will be appended to the current list of InitialTargets. As mentioned, the DefaultTargets attribute is a list of targets to execute if no target is specified; this can be valued from only one project file. The InitialTargets attribute is a list of targets to be executed before any other targets. The value from InitialTargets can be from many different source project files. Following the Project element attribute processing, the child nodes are processed. The same processing is performed if those nodes were immediate children in the importing file. Finally, for the last step after processing the child nodes, the directory is changed to its previous value, and processing on the original file continues. When you import a project file, it is as if you actually took that file and injected its contents inside of the file that was importing it. This is true with the exception of the Project element itself, which was previously discussed, and with issues related to the working directory, which also were previously discussed. How does this affect reusing elements across project files?
67
68
CHAPTER 3 ■ MSBUILD: BY EXAMPLE
When you reuse elements in project files, you have to be aware of what happens in the case of a naming conflict. We have discussed the behavior of values overridden in the same file. Now that you know how project files are imported, we can discuss what happens when project elements are overridden across project files. The behavior of both is the same; all you have to remember is that the last element defined is the one that gives the value. Let’s clear this up with a simple example. The following is the OverrideEx5.proj project file on which you will invoke MSBuild: http://www.sayedhashimi.com <Message Text="Deployment URL: $(DeployURL)"/> This file contains one property, DeployURL, and one target, PrintDeployURL. This file imports the simple ImportOverride1.targets file, which is as follows: http://www.sedodream.com This file simply defines the DeployURL property—the same one defined in the importing project file. You can find the value of this property by executing the PrintDeployURL target. What value do you expect to be printed? Let’s find out by executing the PrintDeployURL target by using >msbuild OverrideEx5.proj /t:PrintDeployURL at the Visual Studio command prompt. Figure 3-20 shows the output from this execution.
Figure 3-20. Output from the PrintDeployURL target
CHAPTER 3 ■ MSBUILD: BY EXAMPLE
As you can see, the value from the ImportOverride1.target file was used instead of the value defined inside the OverrideEx5.proj file. Is this what you expected? This value is used because it was encountered after the first value of DeployURL. Essentially, a project file was processed with the following contents: http://www.sayedhashimi.com http://www.sedodream.com <Message Text="Deployment URL: $(DeployURL)"/> In the previous snippet, the import statement was replaced with the contents of ImportOverride1.target, stripping away the Project element, of course. This is exactly how MSBuild will treat your imported files. If the import statement had been before the DeployURL declaration, then the value http://www.sayedhashimi.com would have been used instead. This behavior is the same for targets that are overridden. You can give it a shot on your own to verify this. If you are importing files and you have properties or targets that you must ensure are defined as is, then you should place them after all import statements. When you execute targets on that file, then you can be sure you are using the definition that you intend. If that file is imported into other files, then you can’t be so sure, but you could throw an error if that condition exists. To summarize, when you share project files, be careful where you place your import statements, and always be aware of how MSBuild will treat them. A good method to mitigate the risk of wrongfully overridden targets is to place hooks into your targets to be overridden instead of overriding the target itself. For instance, Visual Studio provides the BeforeBuild and AfterBuild targets that are empty. Their sole purpose is to be overridden by you. You can employ a similar strategy at your organization. Another method is to use naming conventions; for instance, targets that exist for the sole purpose of being overridden could start with an underscore (_) character.
Dealing with MSBuild Errors As you create new MSBuild targets, you are bound to encounter situations where errors occur. How should you deal with errors, and more important, how can you protect the integrity of the build when one occurs? MSBuild has two elements related to errors: the OnError element and the Error element. OnError specifies what to do in the case of an error, that is, what targets to execute. The Error call actually throws an error and calls any registered error-handling mechanisms. Let’s examine the simple case of handling an error. Refer to the following SimpleError.proj file:
69
70
CHAPTER 3 ■ MSBUILD: BY EXAMPLE
<Error Text="Error in ThrowError target"/> <Message Text="An error has occurred, build will be halted"/> In this project file, you have two targets: one that throws the error, ThrowError, and one that handles errors, MessageErrorHandler. Previously we mentioned that targets can have error handlers associated with them. The OnError element creates this association. This element has only two possible attributes: ExecuteTargets and Condition. The ExecuteTargets attribute is a semicolon-separated list of targets to execute in the case of an error. These targets will be executed in the order they are defined. The Condition attribute is the same here as it is for every other MSBuild element. If the condition is true, then the error handler listed in ExecuteTargets will be registered; if not, then they won’t. Now we’ll demonstrate the execution of the ThrowError target by invoking it with >msbuild SimpleError.proj /t:ThrowError. Figure 3-21 shows the output from executing this target.
Figure 3-21. Output from the ThrowError target
From Figure 3-21 you can see that the ThrowError target was invoked and an error was thrown. Following this, the MessageErrorHandler was invoked to deal with the error. From the output you can see the following line: C:\MSBuild\MSBuildExamples\ErrorExamples\ SimpleError.proj(3,9): error : Error in ThrowError target. This tells you four facts: The file that the error occurred in: SimpleError.proj Where in that file it occurred: (3,9) = Line 3, position 9 What type of error it was: error (more on this in a bit) The error message: Error in ThrowError target
CHAPTER 3 ■ MSBUILD: BY EXAMPLE
When the error is logged, you can use this information to determine what caused it and how to resolve it. Let’s quickly examine the full syntax for the Error element before proceeding. The Error element has five attributes, as summarized in Table 3-4. Table 3-4. Error Element Attributes
Name
Description
Text
Text that is sent to the error handler. If this is not present, then there will be no location information attached with the error; that is, you won’t know where in the project file to start looking.
Code
This determines the type of error and will be sent to the logger. See the following example.
Condition
Condition to determine whether to raise the error.
ContinueOnError
If this is true, then the error will be converted to a warning, and the build will continue.
HelpKeyword
A help keyword that will be sent to the logger.
We will demonstrate how to use the Code attribute in the next error-handling example. The previous example was simple but also usable for small projects. For larger, team-based applications, you may want to have a more flexible approach to error handling. For a better example, see the following Error1.proj project: <ErrorEmails>$(ErrorEmails);
[email protected] CHAPTER 4 ■ EXTENDING MSBUILD
HandleNUnitError <Message Text="Running unit tests in: @(TestAssemblies)"/>
95
96
CHAPTER 4 ■ EXTENDING MSBUILD
<Message Text="NUnitLogFile: @(NUnitLog->'%(FullPath)')" ➥ Condition="'$(NumNUnitFailures)' == '0'"/> <Message Text="NUnitLogFile: @(NUnitFailLog->'%(FullPath)')" ➥ Condition="'$(NumNUnitFailures)' != '0'"/> <Message Text="Num executed tests: $(NumExecutedTests)"/> $(CleanDependsOn); CleanNUnit
97
98
CHAPTER 4 ■ EXTENDING MSBUILD
In the property declaration, you are taking the existing definition of CleanDependsOn and appending to it. Using this mechanism is simple and friendly for other targets that would like to clean their files as well. Consider this scenario: you have two targets that will create files that need to be cleaned; if they both extended CleanDependsOn, then both targets will be executed when a Clean is performed. There is no risk in creating modifications that will overwrite previous modifications because you are always appending to the CleanDependsOn item list. Now you know how to execute the unit tests and how to clean up after that process as well.
Seeing NUnit in Action Now we will demonstrate the execution of the NUnit test execution against a simple project. First we’ll show the project you will be building. This is the DataAccess project. In this project there is an IContact interface, which defines the behavior of your contacts, and a class that implements this interface, which is the Contact class. The interface definition is as follows: namespace DataAccess { public interface IContact { string FirstName { get; set; } string MiddleName { get; set; } string LastName { get; set; } string Email { get; set; } string Website { get; set; } ///<summary> ///Social security number (identifier) /// string Ssn
CHAPTER 4 ■ EXTENDING MSBUILD
{ get; set; } } } IContact is a simple interface that simply exposes some properties. The Contact class implements these properties. Each of the properties will get/set its value to a private data member. The Contact class also provides a useful static method, BuildContacts, to create a list of contacts from a DataSet. The following shows one of the property implementations and the BuildContacts method: public string Ssn { get { return this._ssn; } set { this._ssn = value; } } public static IList BuildContacts(DataSet ds) { IList contacts = new List(); if (ds == null || ds.Tables.Count == 0) return contacts;
DataTable dt = ds.Tables[0]; foreach (DataRow row in dt.Rows) { IContact current = new Contact(); current.FirstName = row["FirstName"].ToString(); current.MiddleName = row["MiddleName"].ToString(); current.LastName = row["LastName"].ToString(); current.Ssn = row["Ssn"].ToString(); current.Website = row["Website"].ToString(); current.Email = row["Email"].ToString(); contacts.Add(current); } return contacts; }
99
100
CHAPTER 4 ■ EXTENDING MSBUILD
The BuildContacts method will simply convert the data contained in the passed-in DataSet, ds, to a list of IContacts. Now that you have seen the classes you will be testing, you’ll examine how to create the tests for them. There are many discussions about where unit testing should go with respect to the code that it is testing. Some prefer to have the testing code in a different solution from the project source code, and others like to have the testing code within the same project to allow white-box testing. In this sample, we will provide the test code in a separate project called DataAccessTest. Where you place your test code will be a decision based on the requirements of the test code and your organization’s rules. The DataAccessTest project contains a single class; this is the DataAccessTest class. One of the ideas behind Test-Driven Development (TDD) is to provide a test for each publicly available method or property. The DataAccessTest class provides this for the Contact class. You have six public methods to perform the testing and one method to set up the environment for the test case. With NUnit, if you apply the SetUp attribute to a method, then this method will be executed before every NUnit test gets executed within that test fixture. In this case, you will use this method to create a list of IContacts before you test each property. The set-up method, SetUpContacts, is as follows: [SetUp] public void SetUpContacts() { FileInfo _tempFile = this.WriteFile(); if (_tempFile == null) throw new Exception("Unable to write the Contacts test file"); DataSet ds = new DataSet(); ds.ReadXml(_tempFile.FullName); this._contacts = Contact.BuildContacts(ds); //now we can delete the file _tempFile.Delete(); } private FileInfo WriteFile() { string fileName = "tempContacts.xml"; string fileText = @" Sayed <MiddleName>Ibrahim Hashimi <Email>
[email protected] <Website>www.sedodream.com Sayed <MiddleName>Yahya Hashimi
CHAPTER 4 ■ EXTENDING MSBUILD
<Email>
[email protected] <Website>www.sayedhashimi.com Mike <MiddleName>Ray Murphy <Email>
[email protected] "; FileInfo theFile = new FileInfo(fileName); File.WriteAllText(theFile.FullName, fileText); return theFile; } The SetupContacts method sets up a new list of contacts before each test is performed. It is important to create NUnit tests that do not depend on any other tests cases and also to create NUnit test cases that are not disturbed by other test cases. This is why you are re-creating the list of contacts before each test method. Since all the test cases are similar, we will show only one. The following is the TestFirstName method: [Test] public void TestFirstName() { IList<string> expectedNames = new List<string>(); expectedNames.Add("Sayed"); expectedNames.Add("Sayed"); expectedNames.Add("Mike"); Assert.AreEqual(this._contacts.Count, expectedNames.Count); for (int i = 0; i < expectedNames.Count; i++) { Assert.AreEqual(expectedNames[i], _contacts[i].FirstName); } } In this method, you are simply ensuring that the FirstName property for the Contact class is being properly set and retrieved. The other test methods are against the remaining public properties of the Contact class. Now you will see how to put everything together. From this point, the only thing left is to integrate the NUnit execution task into this project. To do this, all you have to do is add the following to the project file: <SharedTargetsPath>..
101
102
CHAPTER 4 ■ EXTENDING MSBUILD
Here you are declaring a property, SharedTargetsPath, that will contain all the target files that are shared across targets. In this example, this path is actually the location above the directory containing the project file. In your organization, you may have a location set aside for these types of tasks, and you can inject that path into the SharedTargetsPath declaration. The reason that this is declared as a property instead of an item is because you may choose to get this value from an environment variable instead of placing it into the file itself. If this is the case, you can simply remove the property declaration, and everything should work. Now that you have successfully integrated the NUnit.targets file, you have two new targets that you are able to execute against your projects. Those targets are RunAllTests and CleanNUnit. Actually, another target exists, but it is related to error processing and shouldn’t be called directly. We previously spoke about RunAllTests, so now we’ll show how to execute it and show you the results against the DataAccessTest project. To invoke the tests at the Visual Studio command prompt, you can run >msbuild DataAccessTest.csproj /t:RunAllTests. Figure 4-6 shows the output.
Figure 4-6. RunAllTests target
The RunAllTests target will be executed after a complete build is performed; as you can see, six test cases were executed, and all tests passed. Now to see incremental building at work, execute the RunAllTests target again. Figure 4-7 shows the result.
Figure 4-7. RunAllTests target being skipped
CHAPTER 4 ■ EXTENDING MSBUILD
As you can see in Figure 4-7, the RunAllTests target was skipped. Also notice the time spent during the build. This last execution took 24 milliseconds whereas the previous execution took 2.4 seconds. This is because the RunAllTests targets, and possibly others, were skipped. If you are writing custom target, providing inputs and outputs to support incremental building is essential for builds that execute efficiently and quickly. Now we will move on to discuss how to clean your projects.
Extending the Clean Process As you extend the build process, you are likely to create new files and place them in different places. If you extend the build process, you are also given the work of cleaning up after your extensions. Even if you place files in the OutputPath folder, they will not automatically be cleaned. They will simply be ignored. In this section, we will discuss how MSBuild cleans the files it generates and how you should clean the files you generate. How does MSBuild clean its files? As your project is built, MSBuild keeps track of what files are generated so it knows to remove them on a clean. This file, CleanFile, is stored in BaseIntermediateOutputPath, which is usually the obj\ directory. The CleanFile simply contains the path to files that were generated that need to be cleaned. Files that are eligible for cleaning with this process must be under either OutDir (usually a folder under the bin directory for the current configuration) or IntermediateOutputPath (usually a folder under the obj directory for the current configuration). When the Clean target is invoked, it will examine CleanFile and remove all the corresponding files that are placed under either of those locations. Any files that are generated as an extension to the build process will not be listed in this file and therefore will not be cleaned. To properly clean your files, you have two options: • You can add entries to the clean file. • You can extend the clean process. The easier of these two tasks to complete is the first one, but the more flexible and robust option is to extend the clean process in a similar fashion as you did the build process. We will first discuss how to achieve the first approach because you may see others do this and because it is suitable for most cases. Following this, we will discuss how to extend the clean process by injecting your target to be executed with a call to Clean. To have your files “automagically” removed upon a clean, all you have to do is list the generated files in the MSBuild clean file. To do this, you can simply call the predefined WriteLinesToFile task. Table 4-12 describes the input parameters to this task. Table 4-12. WriteLinesToFile Task Parameters
Parameter
Type
Description
File
ITaskItem
The file to which the content will be written
Lines
ITaskItem[]
The items that will be written to the file
Overwrite
Boolean
If true, existing content will be overwritten; otherwise the file will be appended to
To demonstrate this in use, examine the following project file segment. This segment is taken from the CleanEx1 project. This is a simple C# project that was created to simply demonstrate this cleaning integration.
103
104
CHAPTER 4 ■ EXTENDING MSBUILD
<MyOutputFile Include="$(OutputPath)Myoutput.txt"/> <Message Text="Writing the compile file"/> <WriteLinesToFile File="@(MyOutputFile)" Lines="@(Compile)" Overwrite="false"/> $(CleanDependsOn); CleanNUnit <Message Text="Clean File: $(BaseIntermediateOutputPath) ➥ $(CleanFile)"/> First in this segment you redefine the CleanDependsOn item. This is the list of targets to be executed when a Clean is invoked. From this declaration, notice that you are first getting the current value for $(CleanDependsOn) and then adding CleanNUnit to it. You do this in the same manner as you extended the Build target in previous examples. Following this, you define the CleanNUnit target. This is a simple target that deletes all of the files that the RunAllTests target could have created. Also, an error handler is defined, in case an error occurs during the course of execution.
105
106
CHAPTER 4 ■ EXTENDING MSBUILD
If you execute the RunAllTests target on the DataAccessTest example as shown previously (with >msbuild DataAccessTest.csproj /t:RunAllTests), this will create a file, NUnit.log, and the directory cache, both in the output folder. Now perform a clean to verify that these do get deleted. You can perform a clean with >msbuild /t:Clean. Figure 4-8 shows the results of this.
Figure 4-8. Clean “automagically” removing NUnit-generated files
You can see from Figure 4-8 that the CleanNUnit target was executed automatically and that your files were successfully deleted as expected. Using this method, since a custom target is being executed, you can perform any necessary steps, even break into custom tasks for more complicated steps if necessary. We have now covered all the necessary bases to extend MSBuild to perform all the steps your application requires—and to do it nicely.
Summary In this chapter, we presented the two most extensible aspects of MSBuild: custom loggers and custom tasks. We discussed how you can hook into the MSBuild logging process to create your own logger that meets the specific needs of your application if necessary. We covered how to create custom tasks to perform steps unattainable from existing MSBuild tasks. During this, we also discussed incremental building and how you can create targets and tasks that support it. We showed how to cleanly extend the build and clean process that MSBuild currently has in place. With these tools, you should be able to customize the build to meet the needs of your applications. All of the source code for this chapter is available in the Source Code section of the Apress Web site (http://www.apress.com). For brevity reasons, some details of that code could not be covered; for more information, see the source files.
CHAPTER
5
■■■
Introducing Team Foundation Server and Team Build I
n this chapter, we will introduce some new solutions that Microsoft has created for enterprise organizations. These include Visual Studio 2005 Team Foundation Server (TFS) and Team Build, which are two products in the new Visual Studio Team System (VSTS) product line. Team Build is a powerful component that is built on top of MSBuild. Team Build is a build automation tool included in TFS. The primary goal of Team Build is to provide organizations with a “build lab out of the box.” You need many prerequisites to be able to use Team Build, and we’ll briefly discuss them in this chapter. Beyond this, we will introduce some of the other new components of VSTS, and we will cover how Team Build works and how you can use it to execute custom builds on a dedicated build machine. We will also discuss how you can extend the Team Build build process. Finally, we will discuss how to create an automated public build by using Team Build.
Introducing Visual Studio Team System (VSTS) In this chapter, we will mainly discuss Team Build, which is a component in the new VSTS product line. VSTS is aimed at integrating all parties involved in the development life cycle, from the architect to the developer to the project manager. The team in Team System is the entire team involved in the development effort, not just the development team. VSTS is an effort by Microsoft to raise the bar for its software development tools and to capture more of the enterprise development marketplace. Discussing the details of what VSTS is and how you can use it in your organization is beyond the scope of this text, but we will discuss the components that are relevant to you in this chapter, namely, Team Build and TFS. For more detailed information about VSTS, you can refer to http://msdn.microsoft.com/vstudio/teamsystem. One of the major additions in VSTS is TFS, which is an all-new source control management (SCM) utility. You may be wondering why Microsoft is releasing a new SCM tool when it already has Visual SourceSafe. TFS is targeted to enterprise software companies; Visual SourceSafe is more geared toward smaller corporations. TFS is not just an SCM tool; it introduces some new enterprise features that we will explain throughout the course of this chapter. Note that TFS is required in order to use Team Build and most of what is covered in this chapter.
107
108
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Introducing Team Build If you are using TFS as your SCM tool, you have the ability to add another component to it. This component is called Team Build, and you may have to install this component from your TFS install disc. Team Build is part of a larger product called Team Foundation Build, which is responsible for managing every aspect of team builds. As previously stated, the goal of Team Build is to provide organizations with a “build lab out of the box.” A team build, or public build, is a build created on a dedicated machine that the entire organization can reference later. Team builds are good because the configurations of the build machines are not in question like a developer’s machine typically is, and you typically have a way to re-create these public builds. Team Build is the product you will use to create new public builds and execute them. Team Build is built on top of MSBuild and is extensible just like your typical build is. We will cover how to customize the team build later in the “Extending the Team Build” section. So how do you create a team build? The idea is fairly simple; you have a machine that runs the Team Build service and waits for build requests. Once this happens, then it will get the latest code from the repository and proceed to build the specified product. When this is complete, Team Build will place the results at a specified location, which can be on the build machine or any other accessible network share. Along with building your product, you can also choose to have code analysis and unit tests executed along with your code. Team Build provides this out of the box. If you need to further control the execution, you can customize the team build similarly to how you customize other builds.
Introducing the Team Foundation Build Architecture Before we start discussing in detail how to use Team Build, we will first describe how Team Build fits into the big picture of VSTS, and we will provide you with a look at the architecture behind Team Foundation Build. As mentioned earlier, Team Build is part of Team Foundation Build (see Figure 5-1).
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Figure 5-1. Team Foundation Build architecture
As shown, five primary responsibilities need to be fulfilled. Let’s examine these roles a little more closely.
Team Foundation Build Client Just as you would expect, this is the part of the application that the user interacts with. This is hosted inside Visual Studio. You can perform such tasks as start team builds, view reports for previous builds, or create new build types. A build type is simply a definition for what will be built and how it will be built. For example, you could create a new build type called Release build or Nightly build. You can also specify which users can perform which actions. For instance, you could have a group that is responsible for creating new build types. Other users would not be allowed to create new build types. The security configuration of TFS is an important topic; unfortunately, this is outside the scope of this text. Each client must have the Team Foundation Build client installed; you can find this on the TFS install disc.
109
110
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Application Tier The application tier is the machine responsible for hosting most of the applications. For instance, the source control application will run from this machine, and the Web-based project portals will be driven from this machine. TFS uses a Windows Server 2003–based product for the project portals, which is Windows SharePoint Services 2003. SharePoint Services is a free Web-based tool designed to help share information and increase collaboration amongst team members and with customers. For more information about Windows SharePoint Services, you can visit the Windows Server 2003 home page at http://www.microsoft.com/windowsserver2003/.
Data Tier The data tier is responsible for containing and managing all the data of the projects. For instance, the source control database is located on this machine, along with several other databases. This machine must have SQL Server 2005 installed and running. SQL Server 2005 has many new features that TFS utilizes. For instance, Reporting Services generates the build reports, and you can create your own new reports if you are familiar with this technology.
Build Machine The build machine is a clean machine dedicated to conducting public builds. Typically you will not use this machine for any other purpose besides conducting the builds. This machine must have the Team Build service installed and running prior to any builds being produced on this machine. You can find the setup for this on the TFS setup disc.
Drop Location This is any accessible network share; it will contain the binaries for all the builds. This will include every version that was built for each product. Source code will not be placed on this machine; it will contain only binary files and log files for each build. Typically you will want all the developers in the organization to be able to read from this share.
Using Team Foundation Build In this section, we will discuss how you can use Team Foundation Build to create an automated public build for your projects. It is assumed that you have TFS installed and in use as your SCM tool. For demonstration purposes, we will be using an open source project named Codus. Codus is an object-relational mapping (ORM) tool. You can find more information about Codus at http://adapdev.com/codus/index.aspx. The complete source code for this project is available along with the other source for the exercises. We had to slightly modify the Codus project to fit the needs of a public build. In particular, we placed all third-party assemblies in a common directory that is accessible to all projects and that was checked into source control. To use Team Foundation Build, your project must be part of a team project. A team project is not like the Visual Studio project that you are used to. A team project is part of VSTS and is designed to span multiple solutions. From the team project, you will reference the source control repository, manage work items and project documents, and do much more.
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Now let’s see how you can get started using Team Foundation Build. If you don’t already have a team project defined, then you will need to create one. Follow the steps in the next section to create a new team project.
Creating a New Team Project Open Visual Studio, and select File ➤ New ➤ Team Project. This opens the New Team Project Wizard shown in Figure 5-2. Simply specify the name of the team project, and click Next.
Figure 5-2. New Team Project Wizard
On the next page of the Team Project Wizard, you are asked to select a process template. This defines the development style that this project will use. By default you have two options: MSF for Agile Software Development or MSF for CMMI Software Development. The MSF for Agile template is the template you should use if your organization is using agile development methodologies. Select the MSF for CMMI template if your project requires longer software development life cycles and a more rigorous approach to software development. You can also create new process templates if needed. For this project, select the MSF for Agile template, and then click Next to move to the next page. On the next page, you are asked to give your project portal a title and description. You can fill these in with appropriate values and then click Next to configure the source control settings. On the settings page, you have three options for your source control: • You can create an empty source control folder. • You can create a new source control branch. • You cannot create a source control folder at this time.
111
112
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
For this project, you will go with the default, Create an Empty Source Control Folder. After you click Next, you will see a summary of the team project settings. At this time, you can click Finish to begin creating the team project. After the team project is built, the project portal appears in Visual Studio, as shown in Figure 5-3.
Figure 5-3. Project portal view in Visual Studio
The project portal opens in Visual Studio with the process guidance section. This section describes how you can manage your team project. It covers topics from member roles to team builds. Before you dive deep into team projects, we strongly suggest you examine the process guidance pages for a good description about how to proceed.
Understanding the Team Project Fundamentals Now that you have set up your team project, we’ll cover some of the contents of your team project. If the Team Explorer pane is not visible, you can open it by selecting View ➤ Team Explorer. Figure 5-4 shows the Team Explorer pane.
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Figure 5-4. Team Explorer pane
As you can see from Figure 5-4, the team project has five main elements: Work Items, Documents, Reports, Team Builds, and Source Control. The following sections explain each of these elements.
Work Items A work item summarizes work that needs to be completed on the project. This can be fixing a bug, creating new functionality, writing documentation, and so on. Don’t limit yourself to thinking that work items are just for developers; this is the team project, and every team member can, and should, have work items. From the Work Items element, you can query the work items database for specific conditions.
Documents When developing with previous versions of Visual Studio, you didn’t have a good means to integrate project documents with the projects themselves. Sure, you could place them into a folder with the project, but this was certainly not an ideal solution. With TFS, this issue has been solved. You can place all files that relate to your project in the Documents node under your project. This can include, but is certainly not limited to, documents describing coding standards and documents addressing security vulnerabilities. When you create a new team project, you might be surprised to find many preexisting documents in this node.
113
114
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Reports Reports keep track of your project over a period of time. Many reports are available out of the box, including a report for all team project builds, a report for bug rates, and several others. You can also create new reports; these reports are built on top of Reporting Services. All reports are stored on the server and are available to other team members.
Team Builds This is the section that the remainder of this chapter will focus on. This node displays all your defined team builds, and it allows you to create new team builds. Since you may have different products in this team project, you may have many different team builds available to build against all those products.
Source Control Last but certainly not least is the Source Control section; from this node you can access the source control repository. If you double-click the Source Control item, the Source Control Explorer pane opens in Visual Studio. Currently the Adapdev team project, shown in Figure 5-4, does not have any files in the Source Control section. We will now explain how you can add this project to source control.
Placing Code in Source Control Now that you have an idea of what Team Foundation Build is, we’ll show how to add the Codus project to source control. Before you do this, though, let’s create a work item for this. To create a new work item, right-click the Work Items node in Team Explorer, and select Add Work Item. You also have to specify its type. Name the task Initial Checkin, fill in the other appropriate text boxes, and click Save. Now you can proceed to check the solution into the source control system. Follow these steps: 1. Open the solution in Visual Studio. 2. Right-click the solution in Solution Explorer, and select Add Solution to Source Control. 3. Following this, a dialog box will ask you which team project this belongs to; select Adapdev, and click OK, as shown in Figure 5-5.
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Figure 5-5. Selecting the team project for source control 4. Now you have to commit your changes. Do this by right-clicking the Codus solution in Solution Explorer and selecting Check In. This presents you with the dialog box shown in Figure 5-6. Select Adapdev.
Figure 5-6. Connect to Team Foundation Server dialog box 5. After you click OK, you will see a page that displays the current change set being checked in. From this page you can add check-in notes and associate work items with this check-in change set, as shown in Figure 5-7.
115
116
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Figure 5-7. Source Files view of the Check In dialog box
6. In Figure 5-7, you can see four different views of your change set. These views are Source Files, Work Items, Check-in Notes, and Policy Warnings. You are especially interested in the Source Files view and the Work Items view. The Source Files view shows you the files that have been modified in the current change set and gives you a place to provide a comment about your check-in. The Work Items view allows you to associate this change set with any work items defined. To access any of these views, simply click the appropriate view icon. For this example, you want to associate this check-in with the Initial Check In work item created earlier. Do this by clicking Work Items and selecting the Initial Check In task, as shown in Figure 5-8.
Figure 5-8. Work Items view of Check In dialog box 7. At this point, you are ready to check in your project, so simply click the Check In button.
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Now that you have your project under source control and know a few more things about TFS, let’s move on to Team Build and get started creating a new public build.
Using Team Build Your first goal with Team Build is to get a public build running on a public machine that you can trust to build your products. In this scenario, you will actually be using the machine that TFS is installed on, but this is not recommended. When using Team Build in your organization, you will want a dedicated machine to perform builds. You must install Team Build on this machine; you can find it on the TFS disc. Now you need to create a new team build from Visual Studio. First right-click the Team Builds node, and then select New Team Build Type. You will see the New Team Build Type Creation Wizard, as shown in Figure 5-9.
Figure 5-9. New Team Build Type Creation Wizard
On this page, you should specify the name and description of your team build. For this build, name it Daily Codus build. Later in this chapter we will discuss how you can automate this process. Click Next to proceed to the next page of this wizard, where you’ll select which solutions you want to include in the build. On this page, you pick the only available solution, the Codus solution. Clicking Next brings you to the configuration selection page; for this build, select the Release configuration. On the next page, you will provide information about the build machine, as shown in Figure 5-10.
117
118
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Figure 5-10. Build location information
In this example, since you are actually using the machine that is running TFS, you provide that information in the text boxes. For your team builds, you would provide the information about the dedicated build machine here. You must fill in three fields, as summarized in Table 5-1. Table 5-1. Build Location Parameters
Field Name
Description
Build machine name
This should be the computer name on which you want the build to occur. You could alternatively use the Internet Protocol (IP) address.
Build directory
This is the directory on the build machine where you want the build to take place. Each time you build, you will find the sources used for the most recent build in this directory along with the binaries. Think of this as a temporary directory where the build will occur.
Drop location
This is the network share where the binaries will be saved after each build. Each build will have a unique identifier, so for each build you will be able to retrieve the binaries from this location. This location should be on a machine other than the build machine.
After you fill in this information, click Next to access the build options. On this page, you can specify to run test cases and to perform code analysis. Click Next to go to the summary page. The final page summarizes the team build type you are about to create, as shown in Figure 5-11.
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Figure 5-11. Build type summary page
On this page you can review everything to ensure you did not make any mistakes when defining the build type. Notice that it states the build type will be placed under source control. This means any changes to the build type will be tracked just like your source code. Also, this tells you where the build type file resides. The New Team Build Type Creation Wizard is simply a GUI helper that creates the TFSBuild.proj file. This is the MSBuild file that will drive the team build. Before we discuss this file in great detail, we will discuss how you can kick off a build, and then we will show the results. After completing the New Team Build Type Creation Wizard, you should see a new element under the Team Builds node in the Team Explorer. In this case, this is the Daily Codus build, as shown in Figure 5-12.
Figure 5-12. Team Explorer with new team build
119
120
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
To start the execution of this build, right-click the Daily Codus build element, and select Build Team Project Adapdev. This presents a dialog box that gives you a chance to override some of the configuration settings that you set previously, as shown in Figure 5-13.
Figure 5-13. Team build settings
From here you can select the build type you want to execute, the build machine you want to execute it on, and the build directory of that machine. All other options are not configurable from this dialog box. For now, leave the defaults, and click Build. At this point, you should see a progress dialog box as your build information is sent to the build machine. Once the build progress dialog box disappears, you will see a view of the build in progress. This view will be updated as the build continues on the build machine. Figure 5-14 shows a view of the build starting on the build machine, and Figure 5-15 shows a view of the build progressing on the build machine.
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Figure 5-14. View of build starting on build machine
Figure 5-15. View of build progressing on build machine
The view of the build will continue to expand until the build has completed. The progress bar will continue to move until the build completes. Once the build has completed, the progress bar will disappear, and a few links will appear on the page. When you first create a public build from a solution, don’t be surprised if your build doesn’t pass initially. Typically, developer machines have items that may be forgotten. For instance, you may have installed some third-party libraries in the GAC that are missing from the build machine. This is just one good reason for a public build: you become aware of what it takes to build your project on a clean machine. Figure 5-16 shows the completed build report.
121
122
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Figure 5-16. Completed build report
It seems as though this build failed! As stated previously, when you try to create a public build for a product, you may have to make some modifications to be successful. You should note a few things on this page. You can access the drop site by clicking the build name link. Since the build failed, Team Build automatically opened a new ticket to resolve this issue. You can view this work item by clicking the work item’s link. A summary of the build appears and tells you there were errors and warnings. You can also access the build log by clicking the log link. Since your build failed, you are certainly interested in viewing the log for this build so you can resolve the issues. Here is a section of the build log file: warning MSB3245: Could not resolve this reference. Could not locate the ➥ assembly "log4net". ... GUIConfigurator.cs(7,8): error CS0246: The type or namespace name 'Adapdev' ... After viewing the log, it is obvious that the build machine didn’t have access to the third-party assemblies that are required to build this solution. This is because they were never checked into source control. To add these files to source control, you first need to add the folder directly under the Codus solution folder and then add the files. To do this, you will use a few buttons on the Source Control Explorer toolbar, as shown in Figure 5-17.
Figure 5-17. Source Control Explorer toolbar
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
From this toolbar, click the New Folder button to create the Codus\Shared folder, and then click the Add Files button to add the files to that folder. After you add the files contained in the local directory, your source control directory should look similar to the one in Figure 5-18.
Figure 5-18. Source Control Explorer
You should notice a few things about the Source Control Explorer. The items you added are ready to be checked in, and now a new node appears under your Adapdev team project, the TeamBuildTypes node. This was created when you defined the Codus Daily build. At this point, you can check in the pending changes and rerun the build. When you check this in, you should associate this change set with the bug that was created by the failed build. Then you rerun the Codus Daily build. At this point, if all goes well, the build will succeed, and you can examine the build through the summary page. Now we will discuss how all this happens.
Understanding How Team Build Works By now you are probably wondering how this whole process works. In a nutshell, at the client machine, the team build is initiated. This machine communicates with the app server using Web services. The application server then communicates with the build machine to initialize and run the build. From this point, the build machine conducts all the necessary steps to execute the build, including getting the latest sources from the repository. The build machine reports the status of the build to the application server, which is forwarded by the application tier to the client machine. After the build is complete, the binaries are placed on the drop machine. Now you will look at how Team Build actually builds your project on the build machine. When you create a new team build type with the wizard in Visual Studio, you are actually creating a few files that will be placed into source control. The files that are created and their contents depend on what type of project you have and how you configure the team build type. Three files will be generated each time, and if you are building a C++ project, then another file will be created to assist with those projects. Table 5-2 summarizes these files.
123
124
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Table 5-2. Team Build–Generated Files
Name
Description
TFSBuild.proj
This is the project file that will be built; it contains all the configuration options specified in the New Team Build Type Creation Wizard. This file will define values for your projects. The actual build process is imported through another file, similarly to how desktop builds are imported for managed projects.
TFSBuild.rsp
This is a response file you can use during the team build. In this file you can configure any command-line options that should be sent to msbuild.exe when building your project. By default this is empty.
WorkspaceMapping.xml
This is an XML file that describes which project is being built. This will drive which projects are pulled from source control. You can modify this file, but it is not recommended.
VCOverrides.vsprops
This file is processed by vcbuild.exe to build your C++ projects. If you are not building C++ projects, then this file will not be present.
Of these files, you are most interested in the TFSBuild.proj file; if you need to perform any customizations to the team build, then you will perform them within this file. We will discuss how to customize the team build in a bit. For now we will continue discussing how the team build actually takes place. The TFSBuild.proj file defines your team build, and it defines the build process by importing the details with the following statement: The Microsoft.TeamFoundation.Build.targets file describes all the steps for a public build. You can compare this file to the Microsoft.Commons.targets and Microsoft.CSharp. targets files. When you perform a team build, you have to follow several steps, as shown in Figure 5-19. Note that after the sources are retrieved from source control, they are labeled. This ensures that the same build can be reproduced at any time in the future. When you are working on enterprise applications, this is essential. After the projects are built, the associated work items are updated. The work items are work items that have been resolved since the last good build, which is a build that completes without errors and has all its test cases pass. These work items will have their FixedIn field populated with this build, if it is a good build. If the build fails, then a new work item is generated to resolve the build failure. This is one of the powerful features of VSTS and TFS; all the major tools are very tightly integrated and work well together.
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Figure 5-19. Team build steps
125
126
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Now that you know what the build steps are, you’ll look at the file that drives this entire process. This file is the TFSBuild.proj file that is created with the New Team Build Type Creation Wizard. To view or edit this file, you will have to get it from source control. To do this, open the Source Control Explorer by double-clicking the Source Control node in the Team Explorer pane, and navigate to the TeamBuildTypes folder, as shown in Figure 5-20.
Figure 5-20. Team Build–generated file in Source Control Explorer
From the Source Control Explorer, you can either view the file or check it out for editing. For now you simply view the TFSBuild.proj file by double-clicking it. If you look in this file, you will notice that all the configuration options from the New Team Build Type Creation Wizard are placed in this file. If you would like to change a value from that configuration, you’ll have to change this file manually. You have no way to launch the Team Build Type Wizard for an existing team build type. Here is part of this file, minus all the comments: This is the Codus build that ➥ will be run daily.
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
<BuildMachine>sayed-ws2003 Adapdev <BuildDirectoryPath>F:\Build\Codus \\sayed-ws2003\drops\Codus false <WorkItemFieldValues>Symptom=build break;Steps To Reproduce ➥ =Start the build using Team Build Never true <WorkItemTitle>Build failure in build: This work item was created by Team ➥ Build on a build failure. <BuildlogText>The build log file is at: <ErrorWarningLogText>The errors/warnings log ➥ file is at: From here it is obvious that many of these values came directly from the configuration that you created in the wizard earlier in this chapter. Also note that the first element in this file imports the file that will define the build process, Microsoft.TeamFoundation.Build.targets. When you perform a team build, you are actually executing the EndToEndIteration target. How can you customize this process? You can customize this in the same way you extended the normal build process in previous chapters. This is because Team Build uses MSBuild to perform the builds. In the next section, you will learn about some of the steps you can perform with this process.
Extending the Team Build You previously learned how to extend the build process when you build a project or solution locally. To do this, you used MSBuild to inject steps into the build process. A team build is no different; it has a default way of building team projects, and you are free to extend or modify this process as much as you want. In fact, you can completely replace it if you want. Since you know how to use MSBuild, we don’t need to cover the details of how to write an MSBuild target for Team Build. You simply need to know how and where to inject the appropriate steps into the Team Build process. You will want to place all modifications in TFSBuild.proj. You will have to check this file out to edit it and check it in to make the change take effect. You can also place the modifications in files that get imported into TFSBuild.proj. Remember to check in TFSBuild.proj and all other files that you would like imported into it. You have a few ways to inject your steps into the process that will build your team projects. (We will cover the different methods and discuss the pros and cons of each. This is similar to some material that was discussed in previous chapters.) Many targets are already wired into the build process but have no steps contained within them. For example, before the project is compiled, the BeforeCompile target is invoked. In the Microsoft.TeamFoundation.Build.targets file, this is an empty target. The simplest way to extend the Team Build process is to define those targets. In Table 5-3, you’ll find a list of these “empty” targets. These appear in the order they are defined in the Microsoft.TeamFoundation.Build.targets file.
127
128
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Table 5-3. Team Build Targets Designed to Be Overridden
Name
Description
BeforeEndToEndIteration
EndToEndIteration is the target that drives the whole build process. Override this to perform some action before the entire process.
BuildNumberOverrideTarget
If you do not like the default build numbering provided by Team Build, then you can override it in the target. You’ll need to create a custom task that outputs a property named BuildNumber. This will be used for your build number.
AfterEndToEndIteration
If you need something to occur after everything is completed, you can override this target.
BeforeClean
Called before the workspace is cleaned up. In this target you may want to perform some custom clean steps.
AfterClean
Called after the workspace has been cleaned; you can also perform some custom cleaning steps here as well.
BeforeGet
Called before the sources are gathered from source control. In this step you could get some other files from source control if necessary.
AfterGet
Called after the sources have been copied from source control. If you wanted to change the assembly version before the build process, you could do that here.
BeforeLabel
During a team build, the sources are labeled; this is the target before that process. If you modified any file previously in the build process, you could check them all in at this point.
AfterLabel
Called after the source tree has been labeled.
BeforeCompile
Called before the projects are compiled.
AfterCompile
Invoked after the projects have been compiled.
BeforeTest
This is called before the test cases in the projects are executed. If there were extra steps to set up your test cases, you could implement those steps in this target.
AfterTest
If you would like for some actions to take place after the tests are completed, such as creating summaries of the tests, then you could implement those here.
PackageBinaries
This target is to assist you in deployment; if you wanted to create a zip file of the binaries (or CAB files), you could do that here. If you’d like for these binaries to be located on the drop site, make sure you place them under the BinariesRoot folder.
BeforeDropBuild
The target called immediately before the build is placed in the drop site. If you’d like for other files to be copied to the drop site, this is your last chance to place them under BinariesRoot for copying.
AfterDropBuild
This is called immediately after the files are copied to the drop site. If you wanted to verify that the files made it there, you could implement that in this target.
BeforeOnBuildBreak
When an error occurs, this is the first target that gets called. You could perform any necessary error correction steps here, such e-mailing admins or creating additional work items.
AfterOnBuildBreak
Last target that gets called after an error occurs.
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
By overriding one of these targets, you can easily extend the build process for your team build. We personally do not like overriding these targets, even though they’re there for that sole purpose. As discussed in an earlier chapter, by overriding these targets you expose the potential for a difficult-to-locate hole in your build process. This hole would pop up when the same target is defined in more than one place for the same build. For instance, if the BeforeCompile target is overridden twice, only one implementation can exist; therefore, one is thrown away. This may not seem like a real possibility, but for projects with sophisticated builds, this can happen. Whenever possible, you should write MSBuild extensions that are generic and safely reusable. Implementations that override targets such as these are not safely reusable. To avoid this, you will use another technique. Instead of implementing the predefined targets from Table 5-3 to extend the build process, you should inject your own targets into the build process. Many TFS targets specify the DependsOnTargets value with a list of targets; these targets will be executed before the target that is attached to it. Most of these targets extract this definition from a property, which you can extend to include your own targets. For instance, the following fragment is from the Microsoft.TeamFoundation.Build.targets file: InitializeBuild; PreBuild; Compile; PostBuild; Test; PackageBinaries; TeamBuild is a target that will be invoked during your public build process. This target actually does nothing! It just defines the target that must be executed before it. If you want to add your own steps to this list, you can do something like the following: CustomBeforeTeamBuild; $(TeamBuildDependsOn); CustomAfterTeamBuild; <Message Importance="normal" Text="This is BEFORE the ➥ TeamBuild target gets invoked"/>
129
130
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
<Message Importance="normal" Text="This is AFTER ➥ the TeamBuild target finishes executing"/> This fragment is placed in the TFSBuild.proj file, after the import statement for Microsoft. TeamFoundation.Build.targets. In the property group at the top of this fragment, you are defining the TeamBuildDependsOn property. You are getting the current definition for the property itself with $(TeamBuildDependsOn) and adding to it. Since this is after the import statement, you know that this property has already been defined. Following this property declaration, the two targets are defined. These targets simply print messages to the log. To verify that these customizations worked, you must check in the TFSBuild.proj file and begin another team build. The following is a portion of the log that demonstrates its effectiveness: Target CustomBeforeTeamBuild: This is BEFORE the TeamBuild target gets invoked Target InitializeBuild: Creating directory "F:\Build\Codus\Adapdev\Daily Codus ➥ build\BuildType\..\Sources". ... ... Target GetChangeSetsAndUpdateWorkItems: GenCheckinNotesUpdateWorkItems TeamFoundationServerUrl= ➥ "http://sayed-ws2003:8080/" CurrentLabel="LDaily Codus build_20060109.3 ➥ @$/Adapdev" LastLabel="LDaily Codus build_20060105.3@$/Adapdev" ➥ UpdateWorkItems=True BuildId="Daily Codus build_20060109.3" Querying the contents of label 'LDaily Codus build_20060105.3@$/Adapdev'. Querying the contents of label 'LDaily Codus build_20060109.3@$/Adapdev'. Analyzing labels LDaily Codus build_20060105.3@$/Adapdev and LDaily ➥ Codus build_20060109.3@$/Adapdev. Querying item history. Changeset '56' was included in this build. Changeset '57' was included in this build. Changeset '58' was included in this build. Target CustomAfterTeamBuild: This is AFTER the TeamBuild target finishes executing As you can see from the previous snippet of the build log, your custom targets were executed successfully from the team build. You can extend a series of these DependsOn properties, as listed in Table 5-4.
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
Table 5-4. Available DependsOn Properties
Target Name
DependsOn Property Name
EndToEndIteration
EndToEndIterationDependsOn
InitializeWorkspace
InitializeWorkspaceDependsOn
Clean
CleanDependsOn
TeamBuild
TeamBuildDependsOn
Test
TestDependsOn
PostBuild
PostBuildDependsOn
PackageBinaries
PackageBinariesDependsOn
DropBuild
DropBuildDependsOn
OnBuildBreak
OnBuildBreakDependsOn
CopyLogFiles
CopyLogFilesDependsOn
Using this method, you can confidently and safely extend the process that is executed when your public build takes place. When doing this, you must ensure that these declarations are after the import statement for the Microsoft.TeamFoundation.Build.targets file; otherwise, your changes will be overwritten. Now you will look at how you can customize what happens after an error occurs during the build.
Handling Errors During a Team Build Just like performing a normal build, you always have the possibility that errors will occur; arguably this possibility is actually higher when using Team Build. Handling an error during a team build is different from handling an error during a normal build, however. During the course of a normal desktop build, your main goal is to ensure that the person performing the build is notified that the error has occurred so that it can be resolved. If an error occurs during a team build, this affects the entire team, so you must ensure that the appropriate people get notified to correct the problem and that it is corrected as soon as possible. When your team project is being built, a special target will be called if the build breaks. That target is OnBuildBreak. Typically, this target will create a new work item to have the build error resolved and drop the files at the drop location. But you may want to extend this process to increase awareness of the build failure. For instance, you may want to send an e-mail to the project manager or log the build error somewhere else. Sometimes you would like to extend or replace how a build error is handled. To facilitate this, you first must know the default error-handling mechanism. When you invoke a team build, most of the work actually takes place in the CoreCompile target. This target will set up each project for building. This includes creating output directories, initializing properties, and generating items. It also includes executing the Build target on each project in the team project. Each of these projects will be built in their own instance of MSBuild. If a true build error occurs, then it will occur in this target. As discussed in a previous build, you can specify targets to execute if an error occurs during the execution of a target. You do this using the OnError element. This has to be the last statement of the target. If you look at the bottom of the CoreCompile target, located in the Microsoft.TeamFoundation.Build.targets file, you’ll notice the statement
131
132
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
. This states the OnBuildBreak target will be invoked if an error occurs during the CoreCompile target. Now you will look at the OnBuildBreak target. As mentioned previously, the OnBuildBreak target is invoked by MSBuild if an error occurs during the CoreCompile target. This target, like many other useful targets, actually does nothing! The purpose of this target is to simply invoke other targets to be executed in the correct order. Refer to the following fragment from the Microsoft.TeamFoundation.Build.targets file: BeforeOnBuildBreak; GetChangeSetsOnBuildBreak; DropBuild; CreateWorkItem; AfterOnBuildBreak; As you can see, the OnBuildBreak target simply dictates which other targets get executed, and in what order, by its DependsOnTargets list. Table 5-5 summarizes those targets. Table 5-5. Dependent Targets of the OnBuildBreak Target
Target Name
Summary
BeforeOnBuildBreak
This is an empty target that is designed to be overridden; it’s simply a placeholder.
GetChangeSetsOnBuildBreak
This gets the associated change sets, but obviously the FixedIn field will not be modified during a good build.
DropBuild
This places the build files that were generated on the drop location.
CreateWorkItem
This creates a work item for the build error.
AfterOnBuildBreak
This is an empty target designed for overriding.
From these targets, you can override two of them to add custom functionality; those are BeforeOnBuildBreak and AfterOnBuildBreak. As mentioned, even though this approach will work, a better method is to inject your target by extending the dependency list; in this case, that list is defined by the OnBuildBreakDependsOn property. An example of this is as follows: CustomDoBeforeBuildBreak; $(OnBuildBreakDependsOn)
CHAPTER 5 ■ INTRODUCING TEAM FOUNDATION SERVER AND TEAM BUILD
<ExitCodes> <ExitCode Value="0" Result="Success"/> <ExitCode Value="3010" Result="SuccessReboot"/> The previous code snippet defines a schedule named BeforeProductInstall. The schedule is then referenced by the FailIf install condition, essentially saying, “Fail if the .NET Framework is not installed.” The following is the package manifest for SQL Server 2005 Express Edition: <Package xmlns="http://schemas.microsoft.com/developer/2004/01/bootstrapper" Name="DisplayName" Culture="Culture" LicenseAgreement="eula.txt" > <PackageFiles CopyAllPackageFiles="false"> <PackageFile Name="sqlexpr32.exe" HomeSite="SqlExprExe" PublicKey="public-key-goes-here" <PackageFile Name="eula.txt"/> <Strings> <String Name="DisplayName">SQL Server 2005 Express Edition
209
210
CHAPTER 8 ■ THE CLICKONCE DATA DIRECTORY AND DEPLOYING PREREQUISITES
<String Name="Culture">en <String Name="AdminRequired">You do not have the permissions You have four install conditions. You want to bypass installation of the prerequisite package if the registry entry exists and want to fail if you don’t have an administrator running the installer or a nonsupported version of the operating system. To configure the same thing using the BMG, select Install Conditions; you should see a screen that looks similar to Figure 9-7.
Figure 9-7. Configuring install conditions with the BMG
225
226
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
The BMG shines here because it makes configuring installation conditions easy. You select the type of condition, choose from a selected list of properties, select from a list of compare values (such as less than or equal to), provide an optional value (in cases where it makes sense), and then provide a message. Note that the first condition you check is to see whether the registry entry exists. Not surprisingly, when you named the property ACoolKey on the System Checks tab, the property shows up in the drop-down list in the Installation Conditions tab. Next, you need to configure exit codes for the prerequisite package. Make sure the install file is selected in the tree control, and then choose the Exit Codes tab. Recall that in the previous chapter, you configured the following exit codes: <ExitCodes> <ExitCode Value="0" Result="Success"/> You configured only a success exit code and a failure exit code. You can do the same using the BMG’s Exit Codes tab. As shown in Figure 9-8, you can configure the exit code 0 to indicate a success and then use a default system exit code for failure.
Figure 9-8. Configuring exit codes with the BMG The BMG tool also allows you to add files that may be required by your prerequisite package (such as a CAB file) using the Additional Files tab. You can configure the bootstrapper to run security validations on the install file prior to running the installer. You do this via the Security tab. Figure 9-9 shows both the Additional Files and Security tabs.
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
Figure 9-9. Configuring additional files and security with the BMG
Since the prerequisite package is not signed, you are not going to require the bootstrapper to perform any validation prior to running the installer. You also don’t have any additional files that you need during install, so you are done with configuring the prerequisite package. The last step is to build the package. To do that, click the Build toolbar button above the tree control. This will build the package and copy it to the prerequisite packages folder so that Visual Studio 2005 can display it in the Prerequisites dialog box. (Go to the Project Designer, and then choose the Publish tab. Then click the Prerequisites button.) The Build Results dialog box displays the contents of a build log file for the BMG (see Figure 9-10). Note that if a build is successful, you’ll see the message “Build Succeeded” in the status bar of the Build Results dialog box. Note that there is a link at the top of the dialog labeled Build Output. Clicking this link opens the build output folder, and you can verify that product.xml, package.xml, and the rest of the prerequisite files were created by the BMG tool. You can also verify this by going to the Prerequisites dialog box in Visual Studio 2005.
Figure 9-10. The Build Results dialog box
227
228
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
Using the Manifest Generation and Editing (MAGE) Tool The mage.exe tool is a command-line utility that ships with the .NET Framework 2.0 SDK. You can use this tool to generate and modify ClickOnce deployment and application manifests. Moreover, you can use the tool to sign the aforementioned files. Because mage.exe is a command-line utility, the tool can be useful when scripting deployments or when you need a programmatic interface for creating, editing, or signing deployment and/or application manifests. We already mentioned that this tool also has a UI counterpart (mageui.exe) that provides the same facilities via a Windows Forms application (see Figure 9-11).
Figure 9-11. mageui.exe
You can launch the UI by double-clicking mageui.exe or by executing mage.exe without any parameters. Both of these applications are stored at %programfiles%\Microsoft Visual Studio 8\SDK\v2.0\Bin. The first question that comes to mind with regard to the MAGE tool is, why do you need a tool to generate ClickOnce manifest files when Visual Studio 2005 can create the files and publish your application? Even though Visual Studio 2005 can create these files and publish ClickOnce applications, sometimes you’ll have to manually create and modify them. Prior to diving into using the MAGE tools, it will help if we discuss a few scenarios where this tool is useful.
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
MAGE Scenario: ClickOnce Application Has to Be Deployed to More Than One Server Applications often have to be deployed to more than one server. In this scenario, an application is developed and then has to move through one or more environments prior to arriving at the production server, as depicted in Figure 9-12.
Figure 9-12. Deploying a ClickOnce application to more than one environment
An application starts out in the development environment. In this environment, developers use Visual Studio 2005 to publish the application while building the application. After development is complete, however, the deployment team takes over and is responsible for moving the application through several other environments (to ensure quality) prior to publishing the application to the production environment. Generally, the application goes through at least a testing environment prior to going to production; however, larger organizations have a performance-testing environment, among others. When the deployment team gets the application, the team is responsible for creating the ClickOnce manifest files for each environment. To build these manifest files, deployment engineers can use the MAGE tools. As an application moves from the test environment to the production environment, the ClickOnce manifest files have to be modified and re-signed. You can use the MAGE tool to do this.
MAGE Scenario: The Producer of the Application Doesn’t Know Where the Application Will Be Hosted for Deployment Software companies don’t always know where an application is going to be hosted. For example, a software company builds a ClickOnce application that provides problem-tracking functionality. One of the companies that purchased the software wants to deploy the software to their internal users who do not have an Internet connection; however, they have a connection to the internal network (see Figure 9-13).
229
230
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
Figure 9-13. The software vendor ships the application to the purchaser, and the purchaser deploys the application to its internal server.
In this scenario, the software vendor can create the deployment and application manifest files, but they will have to be modified once the deployment server and update server are known. Note that you are assuming that it is not practical for the software vendor to preconfigure the manifest files for the purchaser of the software because servers often move or have to be renamed, so the software purchaser will need to take responsibility for maintaining the manifest files. In this scenario, the software vendor can produce and distribute a ClickOnce deployable application. The software purchaser can take the ClickOnce deployment, modify the manifest files, and re-sign them using the MAGE tool.
MAGE Scenario: ClickOnce Application Has to Delay-Sign Assemblies The assemblies shipped with the .NET Framework are strong-named, signed assemblies. The CLR verifies the integrity of signed assemblies prior to loading. If an assembly is tampered with, the CLR doesn’t load it. Because of this, some organizations heavily guard the private key used to sign assemblies. If they didn’t and someone with bad intentions obtained the private key, they could sign the assembly and perform malicious activities. For example, if someone got a hold of the private key used to sign the .NET Framework system assemblies (for example, System.dll), they could copy their own version of an assembly and do virtually whatever they liked. Because organizations heavily guard their private keys, developers don’t have access to the private key. This poses the question, if the private key is not exposed to developers to sign assemblies with, how do they build strong-named assemblies? Well, the .NET Framework supports the concept of delay-signing assemblies. Delay-signing assemblies allows developers to tell the CLR not to verify the integrity of an assembly. You can do this by following these steps:
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
1. Use the public key to sign the assembly. The public key is usually in the form of an *.snk file. You can create this file using the signing tool, sn.exe, that ships with the .NET Framework SDK. The signing tool comes with the .NET Framework SDK and is located at %programfiles%\Microsoft Visual Studio .NET 2003\SDK\v1.1\Bin. 2. Apply the strong-named key file to the assembly by adding AssemblyKeyFileAttribute to AssemblyInfo.cs: [assembly:AssemblyKeyFileAttribute("myapp.snk")] 3. Set the delay-signing attribute to true: [assembly:AssemblyDelaySignAttribute(true)] 4. Register the assembly for verification skipping using the signing tool: sn.exe -Vr myapp.dll Delay-signing assemblies should happen only during development. The idea is to skip verification during development and then properly sign the assemblies prior to deployment. The problem with delay-signing assemblies and Visual Studio publishing is that after you do a build of your application, you have to take the generated assemblies and have them signed using the private key. If the assemblies are modified, the ClickOnce manifests (at least the application manifest) have to be re-signed, so you can’t publish an application that is delaysigned using Visual Studio 2005. Instead, you’ll have to use the MAGE tool or a similar tool.
MAGE Scenario: ClickOnce Application Assemblies Need to Be Obfuscated Historically it has been difficult to reverse-engineer the binaries of an application to a form that can be easily understood. With languages such as Java, C#, and others, reverse-engineering has become easy. These languages are compiled to an intermediate form (Java goes to bytecode and C#, and all supported .NET languages go to MSIL), which makes them easy to decompile. More and more, organizations are looking for approaches to prevent the decompilation of their software. Preventing someone from reverse-engineering binaries (whether bytecode, MSIL, or even native machine code1 ) is nearly impossible. Because of this, organizations interested in protecting themselves against decompilation use obfuscation. Obfuscation takes the intermediate form and changes it, such that when decompiled, it becomes difficult to understand. So, what does this have to do with ClickOnce and the MAGE tool? Well, if you have to obfuscate your software, you’ll have to either create the ClickOnce manifest manually or edit the Visual Studio–generated manifest files. The reason for this is that obfuscation is applied to the assemblies. After you compile your application, you obfuscate and then publish. Visual Studio 2005 doesn’t support plugging in obfuscation to this process. As a result, after you obfuscate your assemblies, you have to re-sign manifest files created by Visual Studio. The MAGE tool can help with this.
1. Cifuentes, Cristina, and K. John Gough. “Decompilation of Binary Programs.” Software: Practice and Experience (July 1995): 811–829.
231
232
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
Creating the ClickOnce Manifest Files with the MAGE Tool By now you should understand the usefulness of the MAGE utility. In this section, you’ll learn more about the MAGE tool by walking through the steps required to create the application and deployment manifests with the MAGE tool. You’ll use the GUI version of the MAGE tool (mageui.exe) rather than the command-line version. To start, open Visual Studio 2005, and create a new Windows Forms application named ClickOnceWithMage. When you create a Windows Forms application, Visual Studio 2005 creates a form for you named Form1. Open Form1.cs, and rename this form to MainForm. Do this by right-clicking the name of the class and then choosing Refactor ➤ Rename. Visual Studio will open the Rename dialog box. Name the form MainForm, make sure the Preview Reference Changes checkbox is checked, and then click OK. When you click OK, Visual Studio’s Preview Changes – Rename dialog box opens (see Figure 9-14). Review the changes, and then click Apply.
Figure 9-14. Using Visual Studio 2005’s refactor facilities to rename a class
Next, open MainForm in design view, drag a button from the Toolbox, and drop it on the form. Finally, build the application in Release mode. To create a ClickOnce deployment using the MAGE tool, you have to collect the application files and then create the deployment and application manifests. Open the folder where you stored the application, and then open the bin\Release folder. Notice that Visual Studio 2005 has created an executable named ClickOnceAndMage.exe along with two other files,
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
ClickOnceAndMage.pdb and ClickOnceAndMage.vshost.exe. The first file is obviously the application executable, but what are the other two? These two files are new to Visual Studio and are used for debugging purposes. The *.vshost.exe file, in particular, is interesting. Read more about this file at http://msdn2.microsoft.com/en-us/library/ms185331.aspx. To deploy this application, first create a new folder named MageDeployments. In this file, copy ClickOnceAndMage.exe. Next, go to Start ➤ All Programs ➤ Microsoft .NET Framework SDK 2.0 ➤ SDK Command Prompt. Now type mageui.exe, and press Enter. You should see the MAGE GUI shown in Figure 9-15.
Figure 9-15. The Mage window
The Mage window has a toolbar that allows you to create a new application manifest, create a new deployment manifest, open existing manifest files, and save modified manifests. You’ll start by creating the application manifest.
Creating the Application Manifest You have to create the application manifest first because the deployment manifest has a reference to the application manifest. The leftmost button on the toolbar creates a new application manifest. Figure 9-16 shows the new application manifest UI panes.
233
234
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
Figure 9-16. The UIs used to create/edit application manifest files
Three configuration panes are used to create/edit the application manifest file. These UI panes are Name, Files, and Permissions Required. The Name pane captures high-level details about the application. The interesting panes are the next two. The Files pane identifies the files that make up the application. Because an application can consist of any number of files and file types, the Files pane is designed so that you specify the application directory and click the Populate button to specify the files that make up the application. When you click Populate, the application creates an entry for each file in the Application Files grid. You can use this grid to create optional files and file groups, just like you can when using Visual Studio 2005. The Permissions Required pane allows you to set the CAS security requirements for the application. The drop-down list shown in Figure 9-16 allows you to choose FullTrust, LocalIntranet, Internet, or Custom. FullTrust, LocalIntranet, and Internet are preconfigured with specific permission set/permissions. If you want to specify your own permission set/permissions, you can choose Custom, and then the pane will allow you to enter your own permission requirements. After you fill in these details, you can save the application manifest. When you click the Save toolbar button or choose the File ➤ Save or File ➤ Save All menu item, the application displays the Signing Options dialog box (see Figure 9-17).
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
Figure 9-17. Signing the manifest files
The Signing Options dialog box allows you to sign with an existing certificate or create a new one. Figure 9-17 also shows the Preferences dialog box that you open by selecting File ➤ Preferences. This dialog box configures two global signing options. You can tell the application to sign the manifest file(s) upon saving and also define a default certificate file to use for signing files. This completes the discussion concerning the creation of the application manifest file. We’ll now talk about the deployment manifest.
Creating the Deployment Manifest To create a deployment manifest, click the New Deployment Manifest toolbar button. Figure 9-18 shows the new deployment manifest UI panes.
235
236
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
Figure 9-18. UIs used to create/edit the deployment manifest
The Name and Description panes are self-explanatory. The Deployment Options pane has several interesting features. The first is that you can configure the application type or deployment mode of the application. Recall that you can deploy a ClickOnce application as an offline or online application. An offline application is set to an application type of Install Locally, and online applications are set to Online Only. The second interesting feature on this pane is that you use the Start Location text box to specify where the application is going to be launched from (that is, the ultimate location of the deployment manifest). The Start Location setting is equivalent to the Publish Location setting in the Publish tab of the Project Designer in Visual Studio 2005. The Update Options pane defines how the application will be updated, if at all. Note that this pane captures the same information that the Updates dialog box captures in Visual Studio 2005. The Application Reference dialog box associates the deployment manifest to the application manifest. The Select Manifest button allows you to browse to and select a .manifest file. After you fill out all the panes associated with the deployment manifest and select an application manifest, you can save the deployment manifest. This concludes the discussion of the MAGE application. We’ll now discuss how you can use MSBuild and ClickOnce to make your deployments easier.
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
Using MSBuild with ClickOnce In the previous section, we talked about the MAGE tool. We discussed the GUI version of the tool; however, we mentioned that the tool also has a command-line interface. The commandline interface is useful for situations where you need to script the creation of the ClickOnce manifest files. So when would you need to script the creation of manifest files? Scripting might be helpful in several situations; the primary reason is to automate the entire build and deployment of an application. For example, it’s not uncommon for deployment engineers to script the entire build and publish process. Specifically, the steps might be something like the following: 1. Get the latest version of an application from the source control system. 2. Build the application. 3. Create the ClickOnce manifests. 4. Publish the application. With the MAGE tool, you can do step 3, but the other steps have to be scripted using some other technique. That’s where MSBuild comes in—you can use MSBuild to create a fully customized ClickOnce deployment. To see how, you’ll build a simple Windows Forms application and create the ClickOnce deployment using MSBuild.
Creating the ClickOnce Deployment Using MSBuild Thus far, you have published ClickOnce applications using Visual Studio 2005 and the MAGE tool. Now you’ll see how to do the same using MSBuild. You know from the earlier chapters that you can write an MSBuild script from scratch and execute it. To keep things simple, this example will use the project file generated by Visual Studio 2005. To use MSBuild to create the ClickOnce deployment, you’ll need to perform three steps: 1. Create a Windows Forms application. 2. Set properties on the Publish, Security, and Signing tabs under the Project Designer. 3. Execute the publish target using MSBuild. The first step is obvious, but what about the second step? That too is a requirement because you have to modify the project file with elements such as the following: • The publish URL • Application update configuration • Security requirements • Signing details • Application prerequisites You can guess some of these (via default values) but not others. For example, how will MSBuild know where you want to publish the application? Thus, after you create an application, you need to modify the ClickOnce-related tabs with enough information so that the publish target can execute without errors. To do that, you need to provide at least the following:
237
238
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
1. Provide the publish URL in the Publish tab. 2. Set the application CAS security requirement in the Security tab. 3. Check the Sign the ClickOnce Manifest checkbox on the Signing tab. After you create a Windows application and set the ClickOnce properties, you can use MSBuild to create the ClickOnce deployment. That’s the easy part—it takes only one command: msbuild yoursolutionfile.sln /target:publish This command tells MSBuild to execute the publish target on the given solution. The output of the command is a ClickOnce deployment in the working build configuration (such as Debug). If your build configuration is set to Debug, then you’ll have a yourprojectname.publish folder within the bin\debug\ folder. The contents of the folder will look familiar (see Figure 9-19).
Figure 9-19. An MSBuild-generated ClickOnce deployment
Figure 9-19 shows you how simple it is to create a ClickOnce deployment using MSBuild. Note that in this example, you used Visual Studio 2005 to add the ClickOnce properties to the project file. Having Visual Studio 2005 is obviously not a requirement—you can modify the project file using any text editor. All you need to know is where and how the ClickOnce properties are stored in the project file. To build a ClickOnce deployment using MSBuild, all you need to do is the following: 1. Create a signing certificate (optionally using the signing tool, sn.exe, that ships with the .NET Framework SDK)—this is the certificate that will be used to sign the ClickOnce manifest files. 2. Add the ClickOnce properties to the project file. Do this by adding properties to the first property group defined in the project file. For example, the following code lists some of the ClickOnce properties stored in a typical project file: c:\deployments\
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
true Unc true Foreground 7 Days false false <MapFileExtensions>true \\products\deployments\ true <SignManifests>true true <ManifestKeyFile>WindowsApplication10_TemporaryKey.pfx <ManifestKeyFile>WindowsApplication10_TemporaryKey.pfx 3. Execute the publish target on the project file. The previous steps show how easy it is to use MSBuild to create a ClickOnce deployment. The valuable lesson to take away from this discussion is that there is a predefined target named publish. Moreover, you can customize and automate the ClickOnce deployments using all of the facilities of MSBuild. You’ll now explore how to use MSBuild to automate the generation of a ClickOnce deployment.
Using MSBuild and ClickOnce As Part of a Build Process Thus far, you’ve seen that you can use Visual Studio, the MAGE tool, and MSBuild to create a ClickOnce deployment. With Visual Studio and the MAGE tool, you utilize a user interface where you supply parameters and then click a button to have the implementation generate the ClickOnce manifests. With MSBuild, you saw that you can execute the publish target to generate a ClickOnce deployment. All of these methods serve their purpose; however, one area that is not addressed by the previous methods is generating a ClickOnce deployment as part of a build and deployment process. Figure 9-20 depicts a typical build and deployment process.
Figure 9-20. A typical build and deployment process
Figure 9-20 shows that a typical build and deployment process has three phases: build, assemble, and deploy. In the build phase, a script (for example, an MSBuild-based script) is executed to create binaries from the source code. In the assemble phase, the generated assemblies
239
240
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
are assembled into one or more applications (for example, a smart client or a web application). In the deploy phase, the assembled applications are deployed to one or more servers. Each of the three phases has specific input from the previous phase and generates output, which is used in the next phase. Figure 9-21 shows the input and output of the three phases.
Figure 9-21. The input and output of the three phases of a build process A build and deployment process generally consists of build, assemble, and deploy phases. The build phase takes the source artifacts and generates assemblies. Typically, you either use the Visual Studio solution file or enumerate the project files. The assemble phase uses the output assemblies from the build phase. The assemble phase is responsible for assembling a deployable application from the generated assemblies. After the assemble phase creates deployable applications, the deploy phase does the actual deployment to servers. The build and deployment process is generally an automated process. To realize the benefits of an automated build and deployment process, you have to write an MSBuild script that automatically generates a ClickOnce deployment. For example, if your solution has a smart
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
client along with a Web application in the assemble phase, you need to automate the process of generating the ClickOnce deployment from a list of assemblies. MSBuild defines three tasks you can use to generate a ClickOnce deployment. The ClickOnce-related tasks include GenerateApplicationManifest, GenerateDeploymentManifest, and GenerateBootstrapper. The GenerateApplicationManifest task and the GenerateDeploymentManifest task generate the ClickOnce application and deployment manifests, respectively. The GenerateBootstrapper task generates a bootstrapper. We’ll now show how you can automate the generation of a ClickOnce deployment using these tasks. In this exercise, you’ll use MSBuild to create a ClickOnce deployment. The goal of this exercise is to write an application that has one DLL and one EXE and then write an MSBuild script to create a ClickOnce deployment. You can download the application for this exercise from http://sayedhashimi.com/downloads/book/AutomatedDeployment.zip. The application is a Visual Studio 2005 Windows Forms application consisting of three files: an EXE file (.exe), a support assembly (SupportAssembly.dll), and a configuration file. Figure 9-22 shows the UI for the application.
Figure 9-22. The user interface of the Windows Forms application
The application has one form, which has a button on it. When the user clicks the button, the application calls a method in a class stored in the supporting assembly to get a string. The string is then displayed in a label on the form. To create an MSBuild script, you need to build the application and then take the EXE, the config file, and the DLL and place them in a directory. This step simulates the build phase of an automated build process. In this phase, build the application, and take the two assemblies and the config file and put them in a folder. The MSBuild script will take the files from this folder and will create a ClickOnce deployment from it. This is the assemble phase of an automated build and deployment process. To follow along, create the following folder structure: c:\hold\release. Now put the three files in the release folder. Now that you have the application files where you want them, you can start to build the input for the MSBuild script. As we said earlier, all of the ClickOnce deployments you’ve done so far have been done using Visual Studio 2005, the MAGE tool, or the publish target with MSBuild. Using these techniques, you used UIs to configure the ClickOnce deployment. Automating this process requires that you input all the ClickOnce parameters into the script.
241
242
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
What kind of input do you need? For example, you need to know the update policy of the application. You need to know the location of the certificate used to sign the ClickOnce manifests, the version of the application, and so on. The following list outlines the order: 1. The update URL (has to be HTTP or UNC) 2. The deployment mode of the application (online/offline) 3. The path to a certificate file 4. The application’s update policy 5. The name of the application name and its version 6. A flag to indicate whether to use the .deploy file extension 7. The path to the assembly folder 8. The path to the output folder The update URL defines where clients will be sent for updates. The online/offline property tells ClickOnce whether the application will be installed as an online application or an offline application. ClickOnce manifest files have to be signed, so you need to know how to get to the certificate file. The application name and version are self-explanatory. ClickOnce deployments have three registered file extensions (.deploy, .application, or .manifest). The .application and .manifest extensions are for the deployment and application manifest, respectively. The .deploy extension is for all the files that make up the application. This flag tells the build script whether to apply this file extension to all the files. Note that ClickOnce uses this file extension to circumvent potential problems that often arise because of Web servers configured not to serve specific file extensions (such as .exe).2 The assembly folder tells the script where to find the files that need to be part of the ClickOnce deployment. The output directory property tells the script where to put the ClickOnce deployment after it generates it. Now let’s start the MSBuild script. You’ll start by defining the properties you need for your ClickOnce deployment: http://localhost/AutomaticDeployTakeOne/AutomaticDeployTakeOne.application <ApplicationVersion>1.0.0.0 <UseDeployExt>true <ApplicationPublisher>Sayed Y. Hashimi <ApplicationSupportUrl>http://localhost/ <EntryPointAssembly>AutomaticDeployTakeOne.exe C:\hold\release\ c:\inetpub\wwwroot\AutomaticDeployTakeOne\
2. For more about the .deploy file extension, see http://msdn2.microsoft.com/en-us/library/ms165433.aspx.
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
<ApplicationName>AutomaticDeployTakeOne AutomaticDeployTakeOne.exe.config <ApplicationDescription> Automating ClickOnce Deployment Generation We hinted at most of the properties earlier; however, a few require special attention. The CertificateThumbprint property contains the thumbprint of the certificate that will be used to sign the ClickOnce manifest files. You can obtain the thumbprint of a certificate by looking at the certificate in certmgr.exe. The certmgr.exe application displays the installed certificates (for the logged-in user). If you have the .NET Framework SDK installed, open a Visual Studio command prompt, and enter certmgr.exe. To obtain the thumbprint of a certificate, select a certificate, click the View button, and then select the Details tab, as shown in Figure 9-23. From the Details tab, scroll down until the Thumbprint field is visible. Choose Thumbprint, select the contents from the text box, and press Ctrl+C to copy the thumbprint. You can then paste the thumbprint in your build script.
Figure 9-23. The certmgr.exe user interface
Now that you have defined your properties, you need to define a few MSBuild items that will capture the files that make up the application. These items, along with the properties, will be used as input to the ClickOnce-related MSBuild tasks: <EntryPoint Include="$(BinFolder)$(EntryPointAssembly)"> Managed Install
243
244
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
Managed Install The previous snippet shows three items: EntryPoint, Dependency, and ConfigFile. The EntryPoint item defines the EntryPoint assembly (that is, the EXE that contains the Main method). The Dependency item refers to the support assembly (SupportAssembly.dll), and the ConfigFile item points to the application configuration file. Now let’s see the task that generates the application manifest: The GenerateApplicationManifest task outputs a ClickOnce application manifest. The OutputManifest property of the task defines the location where the manifest is generated. In this example, you have followed the naming convention used by Visual Studio 2005. Namely, you have used the .exe.manifest naming convention. The EntryPoint property points to the application executable, and the Dependencies property points to the collection of dependent assemblies. The ConfigFile property defines the application configuration file. The rest of the properties are self-explanatory. Now let’s see how you can generate the deployment manifest:
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
The GenerateDeploymentManifest task generates a ClickOnce deployment manifest. As expected, the deployment manifest defines properties that capture the update policy of the application and the location where updates are obtained. The MapFileExtensions property is a flag that tells the tasks whether to use the .deploy file extension. Similar to GenerateApplicationManifest, this task defines an OutputManifest property that tells the task where to generate the deployment manifest and what to name the file. Note that the deployment manifest has to have a reference to the application manifest. You specify the application manifest using the EntryPoint attribute on the GenerateDeploymentManifest task. In the previous example, you are using the usual convention of naming deployment manifests with the .application extension. Earlier we talked about signing manifests. After you define the task that generates the application or deployment manifest, you can use the SignManifest task to sign the manifest with a certificate: <SignFile CertificateThumbprint="$(CertificateThumbprint)" SigningTarget="@(DeployManifest)"/> The SignFile task takes a certificate thumbprint and a manifest file and signs the manifest with the certificate. The MSBuild script for this example is included with the solution, and both are packaged in a file at http://sayedhashimi.com/downloads/book/AutomatedDeployment.zip. Note that for the script to work, you’ll need to paste in a certificate thumbprint into the script.
Looking at Some Common ClickOnce Scenarios We’ll now talk about some common scenarios with regard to ClickOnce deployments.
Passing Parameters to a ClickOnce Application ClickOnce supports a mechanism where you can pass the ClickOnce-deployed application parameters. By default, this option is not enabled when you configure an application for deployment using Visual Studio 2005. To enable arguments to be passed to your application, go to Project Properties ➤ Publish, and click the Options button. In the Publish Options dialog box, check the Allow URL Parameters to Be Passed to Application option. To pass parameters, you can then construct the URL to your deployment manifest with URL parameters such as http://localhost/ClickOnceURLParams/ClickOnceURLParams.application?username=sayed& password=mypass. You can retrieve URL parameters by using the HttpUtility class in the System.Web assembly. The following code snippet demonstrates this: private void DisplayURLParameters() { try { Uri appUri = System.Deployment.Application.ApplicationDeployment. CurrentDeployment.ActivationUri; if (appUri != null) {
245
246
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
NameValueCollection parms = HttpUtility.ParseQueryString(appUri.AbsoluteUri); if (parms != null) { paramCountLbl.Text = parms.Count.ToString(); usernameLbl.Text = parms[0]; pwdLbl.Text = parms[1]; } else { MessageBox.Show("no parameters passed to application"); } } } catch (Exception ee) { MessageBox.Show(ee.Message); } } The DisplayURLParameters method gets the URI to the application using the ActivationUri property. The method uses the static ParseQueryString method to parse the query string. You can download the sample application from http://www.sayedhashimi.com/downloads/ ClickOnceURLParams.zip. The MAGE tool can also create a ClickOnce deployment to support passing URL parameters (similar to Visual Studio 2005). If you are generating a ClickOnce deployment using MSBuild, then you’ll need to set the TrustUrlParameters property to true when you create your GenerateDeploymentManifest task. This property is set to false by default. Note that there is a caveat to passing URL parameters to a ClickOnce-deployed application: ClickOnce does not support passing URL parameters to the application if the application is activated from the Start menu shortcut. In other words, you have to activate the application using a URL. If the user starts the application by going to the Start menu, you will not get the URL parameters. The URL parameter-passing feature works really well with online applications. Recall that online applications don’t get Start menu shortcuts (or an entry in Add/Remove Programs) and are always activated via the URL. With offline applications, the URL parameter passing works particularly well in situations where the application is passed in a username and password (and possibly other parameter) on the URL to bypass the user having to log in.
Installing the Publisher Certificate Programmatically with a Prerequisite Chapter 7 discussed using trusted publishers with ClickOnce. We said that when your application’s deployment manifest is downloaded to the client, the ClickOnce runtime sees whether the publisher certificate of the application is in the Trusted Publisher store for the logged-in user. If the trusted publisher certificate is found, then it verifies that the certificate authority that issued the certificate is in the Root Certificate Authority store. If both of these conditions pass, then ClickOnce does not display the Unknown Publisher security dialog box to the user.
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
In Chapter 7 we showed that you can manually take a certificate and install it in the two certificate stores using Visual Studio 2005. In an enterprise environment, it’s not possible to get to all of the clients and do this step. Generally, you do this using a network setup package (such as Tivoli or SMS) prior to users installing the application. If this is possible in your environment, then this option is preferable. If not, you can install the certificate that your application is signed with to the two stores using a custom prerequisite. The idea here is to write a small MSI that uses a custom installer action to put the certificate into the desired stores. Here is a custom installer action class that does this: using using using using using using
System; System.Collections.Generic; System.ComponentModel; System.Configuration.Install; System.Security.Cryptography.X509Certificates; System.Reflection;
namespace Installers { [RunInstaller(true)] public partial class CertInstaller : Installer { // the certificate is packaged as // part of this assembly. The following path refers // to the certificate using a fully qualified name. private static readonly String CERTIFICATE_PATH = "Installers.deploycert.cer"; public CertInstaller() { InitializeComponent(); } public override void Install(System.Collections.IDictionary stateSaver) { base.Install(stateSaver); // we have to put the certificate in two stores: // the Trusted Root Certification Authorities store and // the Trusted Publisher store. Stream certStream = null; X509Store rootStore = null, publisherStore = null; try { // get a reference to the root store rootStore = new X509Store("Root", StoreLocation.LocalMachine); if (rootStore != null) { // open it with write access
247
248
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
rootStore.Open(OpenFlags.ReadWrite); // get a reference to the Trusted Publisher store publisherStore = new X509Store("TrustedPublisher", StoreLocation.LocalMachine); if (publisherStore != null) { // open it with write access publisherStore.Open(OpenFlags.ReadWrite); // get the certificate. Note that the certificate // is an embedded resource in this assembly. Assembly asm = Assembly.GetAssembly(this.GetType()); if (asm != null) { certStream = asm.GetManifestResourceStream (CertInstaller.CERTIFICATE_PATH); if (certStream != null) { // read the certificate BinaryReader reader = new BinaryReader(certStream); byte[] certData = reader. ReadBytes((int)certStream.Length); // create a certificate object X509Certificate2 cert = new X509Certificate2(certData); if (cert != null) { // add it to the root store rootStore.Add(cert); // add it to the Trusted Publisher store publisherStore.Add(cert); } } } } } } finally { if (rootStore != null) { rootStore.Close(); } if (publisherStore != null) { publisherStore.Close(); }
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
if (certStream != null) { certStream.Close(); } } } } } This installer class overrides the Install method of the base class to install the certificate to the two stores. As shown, the method gets references to the Root Certificate Authority store and the Trusted Publisher store (using the X509Store class) and then adds the certificate (an instance of the X509Certificate2 class) to each store. Note that if the certificate already exists in these stores, calling the add method again doesn’t create a duplicate.
Creating File Type Associations for ClickOnce Deployments Windows Forms applications often work with various file formats. For example, an application that creates picture albums or allows for picture manipulation likely supports the Joint Photographic Experts Group (JPEG) file format and the Graphics Interchange Format (GIF). Similarly, it’s not rare for an application to produce/consume a proprietary file format. When this is the case, it helps if the deployment technology supports registering the various file types during the initial install of the application. Unfortunately, ClickOnce does not support file type registration out of the box. You can solve this problem in a couple of ways, however. The first, and recommended, approach is to use a custom prerequisite that does the registration for you. The second option is to take advantage of the following well-known facts: • After ClickOnce installs an application, it immediately launches it. • The ClickOnce APIs provides a way for you to detect whether the application is running for the first time. You can use the IsFirstRun property on the ApplicationDeployment object to determine whether the application is running for the first time: if (System.Deployment.Application. ApplicationDeployment. CurrentDeployment.IsFirstRun) { // do file registration here } If IsFirstRun returns true, then register the file types that your application works with. This approach is not recommended, however, because it creates an additional problem for you when the application needs to be removed from the machine. That is, when the application is uninstalled, how do you unregister the file associations? ClickOnce doesn’t provide a mechanism for you to plug into the install, so creating file associations in this manner ends up with a dirty uninstall scenario. Therefore, it is recommended that you stick with using a custom prerequisite.
249
250
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
Both of these approaches have a compromise. Using a prerequisite package, for example, requires the user installing the application to have administrator privileges (this is a Windows Installer requirement). The second option requires full trust because, at the least, you need to query the IsFirstRun property, which demands full trust.
Creating a Desktop Icon for a ClickOnce-Deployed Application ClickOnce does not support creating desktop icons. If this is a requirement, you can take advantage of the same principles we talked about earlier. That is, you can either opt to use a prerequisite package or use the ClickOnce APIs. Again, consider the side effects before jumping in.
Requiring a Prerequisite After the Initial Install We’ve talked at length about deploying prerequisites with ClickOnce applications. You know that when an application is installed for the first time, the user can run a bootstrapper package that can check for and install the application’s prerequisite list. This works great for the initial install, but what happens if an application develops a prerequisite in a later version? That is, what happens if you deploy version 1.0 and then in version 2.0 you need an additional prerequisite? For most applications, users will likely activate a ClickOnce application either by going to the Start menu shortcut or by clicking a link that points to the deployment manifest. In these scenarios, you’ll have to account for how the user needs to be instructed to download and install the new prerequisite. You have a couple of options.
Option 1: Use a Customized Launch Page and Have Users Always Launch the Application by Running the Bootstrapper Package The general procedure of deploying a ClickOnce application is to write the application, create its prerequisites, and then publish the application to, for example, a Web server. Users are then sent a link to the deployment manifest or a launch page for the application. For example, this can be something similar to the publish.htm page that Visual Studio 2005 generates. Most organizations generally customize the launch page and send users the link to this page rather than distributing the link to the deployment manifest. The launch page serves several purposes: • It can be used to give users an overview of the application. • It tells users about the prerequisites of the application and how to run the bootstrapper that can install them, prior to running the application. • It gives users information about future versions and possibly helpful hints. • It offers security warnings. Having a launch page like this not only provides the previous benefits, but you can use it to instruct users to click a link that always kicks off the boostrapper rather than a link that runs the deployment manifest. This ensures that if an additional prerequisite is added to an update, the prerequisite will be installed prior to running the application.
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
Option 2: Deploy an Update to Notify Users to Install the Prerequisite It may not be desirable to have users always come to the launch page to run your application, especially with applications that support offline capabilities where users always launch the application from the Start menu shortcut. In these scenarios you can take a different approach. You can deploy a tiny update to your application, ahead of the actual update, to simply instruct users of the prerequisite for the next version. For example, you could create a What’s Coming dialog box and have instructions for users to go to the launch page to run the bootstrapper setup package. This option is attractive because you can do sophisticated checks to ensure that all of your users have run the bootstrapper package. For example, you deploy an update with the What’s Coming dialog box. This dialog box provides an overview of what is coming in the next version and provides a link for more information about the next version. The link points to the launch page. When the user browses to the launch page, you can run server-side code to record that the user read the page and if the user clicks a button or a link to run the bootstrapper to install the prerequisite, you can then run server-side code to record that the user ran the setup package. Moreover, in your application you can check to see whether the prerequisite has been installed and then decide to continue to show the What’s Coming dialog box or not. Once you think all (or most) users have installed the prerequisite, you can then deploy the real update.
Option 3: Wait Until the Prerequisite Is Used and Then Show the User a Message After an Exception Checking for prerequisites can be as simple as running a piece of code that uses the prerequisites within a try/catch block. If you get an exception, you can direct the user to the launch page, discussed in the “Option 1” scenario, and have them run the bootstrapper package. Again, all of the options have pros and cons and thus require considerable attention to ensure that users receive a seamless update experience (if possible).
Deploying a ClickOnce Application from a CD/DVD With ClickOnce, you can perform the initial install of your application using removable media (such as a CD or DVD) while providing updates from a Web server or file server. When would you want to do this? ClickOnce provides this feature to support the following deployment scenarios: • You have a large project, and it is unreasonable for your users to suffer through the initial install of the application. • You have a medium/large deployment, but your user base has a slow network connection. • Your users have no network connection or their network connection is not stable (they don’t have connectivity all the time). As you can see, in all these cases it would be nice if you could put your ClickOnce deployment on a CD/DVD and at the same time take advantage of automatic updates provided by ClickOnce. ClickOnce supports this by allowing application publishers to specify an update location (a file share/Web server). Here are the steps to produce an application that can be installed from a CD/DVD and updated from a network share/Web server:
251
252
CHAPTER 9 ■ CLICKONCE TOOLS AND SCENARIOS
1. Create a new folder at c:\testpublish\. 2. Create a new Windows Forms application in Visual Studio 2005. 3. Open the Project Designer, and go to the Publish tab. 4. Set the Publish Location to c:\testpublish\. 5. Click the Publish Wizard button. 6. Choose Next in the first dialog box. In the How Will Users Install the Application dialog box, choose From a CD-ROM or DVD-ROM. Press Next. 7. In the Where Will the Application Check for Updates dialog box, either enter a network share or create a virtual directory under a Web site that you have used to deploy ClickOnce applications. ClickOnce will use this URL to detect and download updates. 8. In the Ready to Publish dialog box, choose Finish. Visual Studio will then publish the application to c:\testpublish\. You can then take the application and burn it onto a CD/DVD. Take the CD/DVD to another machine and run the setup.exe bootstrapper to install the application. Note that because the entire application is copied to the CD/DVD, your users don’t need a network connection to install the application. To get updates, however, a network connection is a necessity. If you want to test how an update will work, then after you install the application, publish an update to your update folder and then start the application.
Summary In this chapter, we discussed three important tools related to ClickOnce. We talked about the Bootstrapper Manifest Generator (BMG), the Manifest Generation and Editing (MAGE) tool, and MSBuild. All of the discussions in this chapter related to practical problems. For example, we showed how you can use the BMG tool to quickly create the product and package XML files needed to create custom prerequisites. The discussions about the MAGE tools gave four practical scenarios where the MAGE tool can be useful, and the discussion of MSBuild showed how to automate the generation of a ClickOnce deployment. All of these tools will be useful as you work with ClickOnce. The last portion of this chapter talked about some practical ClickOnce scenarios.
Index ■Symbols \ backward slash, 56 % character, 48 % notation, 59–62 @ notation, 59–62 ; semicolon, 46 $() syntax, 59 _ underscore character, 69
■A ActivationUri property, 246 AfterBuild target, 32 AfterClean target, 32, 128 AfterCompile target, 32, 128 AfterDropBuild target, 128 AfterEndToEndIteration target, 128 AfterGet target, 128 AfterLabel target, 128 AfterOnBuildBreak target, 128, 132 AfterPublish target, 33 AfterRebuild target, 32 AfterResGen target, 33 AfterResolveReferences target, 33 AfterTest target, 128 Agile template, 111 AL task, 37 aligning elements, 49–53 Ant, 23 application architectures, 14–16 .application file extension, 145, 242 application files, 185–190 application launcher controller, 143 application manifests, 145, 154, 163 creating, 233 viewing dependencies and, 186 application state, persisting, 192 application tier, Team Foundation Build and, 110 applications. See also deploying applications building with MSBuild, 45–74, 237–245 full trust, 169 isolated application installation and, 138 partial trust, 175–180 types of, 1–3 updating, 141, 166–169 AppStart.exe file, 143 AppStart.exe.config file, 143 architectures, 14–16
ASCII character codes, 49–53 .asmx files, 9 AspNetCompiler task, 37 assemblies, 4 delay-signing, 230 obfuscating, 231 assembly element, 162 assemblyIdentity element, 162 assertions, NUnit and, 86 AssignCulture task, 37
■B backward slash (\), in project filenames, 56 beforeApplicationStartup element, 163 BeforeBuild target, 32 BeforeClean target, 32, 128 BeforeCompile target, 32, 128 BeforeDropBuild target, 128 BeforeGet target, 128 BeforeLabel target, 128 BeforeOnBuildBreak target, 128, 132 BeforePublish target, 32 BeforeRebuild target, 32 BeforeResGen target, 33 BeforeResolveReferences target, 33 BeforeTest target, 128 BeginEndToEndIteration target, 128 bin folder, 6 binaries, drop location for, 110, 118 BITS (Binary Intelligent Transfer Service), 144 BLL (business logic layer), 14 BMG (Bootstrapper Manifest Generator), 219–227 bootstrapper (Visual Studio), 157 configuring, 182 prerequisites and, 181, 201–205, 210–218 UAB and, 142 BSD make-style build tools, 22 build directory, 118 build events, 84 build location parameters, 118 build logs, 75–106 build machines, 110, 117 build tools, 21–43 build types, 109 build.xml, 23 BuildEventArgs class, 84 BuildEventArgs subclasses, 77 253
254
■INDEX
BuildNumberOverrideTarget, 128 business logic layer (BLL), 14
■C CAS (Code Access Security), 173 CAS sandboxes, 154 CDs, deploying ClickOnce applications from, 251 characters, escaped, 48 CheckForUpdateCompleted event, 198 CheckForUpdateProgressChanged event, 198 clean process, 103–106 NUnitTask and, 97 Clean target, 32, 97, 104, 131 CleanDependsOn property, 97, 131 CleanFile, 103 ClickOnce, 5, 144–159 architecture of, 145 common deployment scenarios and, 245–252 deploying prerequisites via, 181–183 desktop icons and, 250 language attribute and, 162 on-demand download facility in, 186 as part of build process, 239–245 security and, 154–159, 169–181 steps in, 148 tools and, 219–252 updating applications via, 166–169 updating/versioning and, 151 ClickOnce APIs, 186, 155–157, 196–201 ClickOnce cache, 165 ClickOnce data directory, 190–201 ClickOnceAndAccess sample application, 193–196 client-server architecture, 14 CMake (Cross Platform Make), 23 CMMI template, 111 code groups, 174 code, placing in source control, 114–117 Codus, 110, 114–123 Compile target, 32 ComputeClickOnceManifestInfo target, 33 computerwide controller, 143 Condition attribute, 26 ConfigFile property, 244 configuring partial trust applications, 176 prerequisites, 181, 204 trusted publishers, 173 trusted sites, 219 updates, 167 Windows services, 11 console applications, 2, 13 console logger, 75 Contact class, 98–101 content-based evidence, 175
controllers, UAB and, 142 Copy task, 37 CopyLogFiles target, 131 CopyLogFilesDependsOn property, 131 CoreBuild target, 32 CoreCompile target, 32 CreateItem task, 37 CreateProperty task, 37 CreateSatelliteAssemblies target, 33 CreateWorkItem target, 132 creating applications, with MSBuild, 45–74, 237–245 deployment manifests, 235 desktop icons, for ClickOnce applications, 250 loggers, 77–85 manifest files, via mageui.exe, 232–236 MSBuild files, with IntelliSense, 54 prerequisites, 210–216, 221–227 project files, 24 targets, 27 tasks, 38–42 Cross Platform Make (CMake), 23 Csc task, 37 custom metadata, 57
■D DAL (data access layer), 14 data files, 185, 190–200 data tier, Team Foundation Build and, 110 DataProtectionPermission, 177 Debug in Zone feature (Visual Studio), 177 delay-signing assemblies, 230 Delete task, 37 dependencies, viewing, 186 Dependencies property, 244 dependency element, 163, 187–190 DependsOn properties, 130 .deploy file extension, 242 deploying applications file type associations and, 249 incremental building and. See incremental building MAGE tool scenarios and, 228–231 MSI deployment and, 139 online/offline, 164 no-touch deployment and, 140 prerequisites for, 1–19 from removable media, 251 side-by-side deployment and, 138 strategies for, 17 via ClickOnce, 144–159, 237–252 deployment element and, 162, 169 deployment manifests, 145, 161–164 creating, 235 modifying, 169
■INDEX
deploymentProvider element, 163, 165 description element, 162 descriptor files, 206 desktop icons for ClickOnce applications, creating, 250 .dll files, 4 DnsPermission, 176 Documents node (Visual Studio Team Explorer pane), 113 DotNetFreeCell, 76 DownloadFileGroupCompleted event, 198 DownloadFileGroupProgressChanged event, 198 drop location, 110, 118 DropBuild target, 131 DropBuildDependsOn property, 131 DVDs, deploying ClickOnce applications from, 251 EndToEndIteration target, 128, 131 EndToEndIterationDependsOn property, 131 entryPoint element, 164 EntryPoint property, 244 environment variables, 63 EnvironmentPermission, 176 Error element, 69, 71 error handling, Team Build and, 69–73, 131 Error task, 37 escaped characters, 48 EventLogPermission, 177 evidence, 174 .exe files, 4, 242 Exec task, 37 expiration element, 163
■F file-based loggers, 78 file element, 187–190 file logger, 75 file type associations, 249 FileDialogPermissionson, 176, 178 FileIOPermission, 176 FileLogger parameters, 76 FileLoggerBase class, 81 filenames, backward slash (\) in, 56 FindUnderPath task, 37 formatting output, 48–53 FreeCell game, 76 full trust applications, 169 vs. partial trust applications, 179
■G GAC (global assembly cache), 5, 190 GBS (GNU Build System), 22 GenerateApplicationManifest target, 33
GenerateApplicationManifest task, 37 GenerateBootstrapper target, 33 GenerateBootstrapper task, 37 GenerateDeploymentManifest target, 33 GenerateDeploymentManifest task, 37 GenerateResource task, 37 GetAssemblyIdentity task, 37 GetChangeSetsOnBuildBreak target, 132 GetFrameworkPath task, 38 GetFrameworkSdkPath task, 38 global assembly cache (GAC), 5, 190 GNU Build System (GBS), 22 GNU Make, 22 Gotdotnet.com, 219
■H hash element, 190 HelloFromClickOnce, 148–151 HelloTask, 39–42 hosted applications, 2, 13
■I IContact interface, 98–101 IEventSource interface, 77 ILogger interface, 77 Import element, 25 incremental building, 25, 29, 94–97 InitalizeWorkspace target, 131 InitalizeWorkspaceDependsOn property, 131 Initialize method (ILogger), 79 inputs, 90, 94–97 IntelliSense (Visual Studio), creating/editing MSBuild files with, 54 IntelliSense in the Zone feature (VB.NET), 178 Internet Explorer, configuring trusted sites and, 219 IsFirstRun property, 249 isolated application installation, 138 IsolatedStorageFilePermissions, 176 ITask interface, 91 item values, 59–62 ItemGroup element, 25 items, 25, 45
■J Jam and Cook, 22
■K KeyContainerPermission, 176
■L languages, ClickOnce and, 162 LC task, 38 Logger class, 77 Logger events, 78
Find it faster at http://superindex.apress.com/
■E
255
256
■INDEX
logger switch, 76, 80 LoggerException, 85 loggers, 75–106 file-based, 78 specifying additional, 75, 80 troubleshooting, 85 writing, 77–85
■M mage.exe tool, 228–236 mageui.exe tool, 147, 228, 232 make-style build tools, 22 MakeDir task, 38 .manifest file extension, 145, 242 manifest files, 161–164, 179 ClickOnce and, 145 creating via mageui.exe, 232–236 UAB and, 142 writing, 212 Manifest Generation and Editing (MAGE) tool. See mage.exe tool; mageui.exe tool MapFileExtensions property, 245 Message task, 38 metadata, 39 custom, 57 well-known, 45–48, 57 Microsoft Program Maintenance Utility (NMAKE.EXE), 22 MSBuild, 24–43 building applications with, 45–74, 237–245 errors and, 69–73 integrating into Visual Studio, 55 Team Build and, 107 MSBuild files cleaning, 103–106 elements of, 24–25, 63–69 MSBuild loggers, 75–106 MSBuild projects, 220 MSBuild task, 38 MSF for Agile template, 111 MSF for CMMI template, 111 MSI deployment, 139
■N n-tier architecture, 15 Name attribute, 27 naming conventions, 64, 69 console application configuration files and, 13 NAnt, 23 .NET CF (.NET Compact Framework), 10 .NET platform, 23, 24 .NET runtime, downloading, 17 NMake, 22 NMAKE.EXE (Microsoft Program Maintenance Utility), 22 NTD (no-touch deployment), 140
NUnit framework, 85–89 NUnit GUI runner, 88 NUnit.targets file, 90, 92 NUnitTask, 89–94 executing, 98, 103 NUnitTask files, cleaning, 97 NUnitTask items, 93 NUnitTask.cs file, 90
■O obfuscation, 231 offline deployments, 164 OleDbPermission, 177 on-demand download facility (ClickOnce), 186 OnBuildBreak target, 131–133 OnBuildBreakDependsOn property, 131 one-tier/two-tier/three-tier architecture, 15 OnError element, 69 online deployments, 164 Opus Make, 22 origin-based evidence, 175 output, formatting, 48–53 OutputManifest property, 244 outputs, 90, 94–97
■P Package Manifest projects, 220 package manifests (package.xml), 205–210 writing, 212 PackageBinaries target, 128, 131 PackageBinariesDependsOn property, 131 Packages folder, 205, 214 parameters, 91 passing to ClickOnce applications, 245 Parameters property (ILogger), 78 partial trust applications, 175–180 PerformanceCounterPermission, 177 PermCalc.exe utility, 178 permission calculators, 177 permission sets, 174 permissions, 174–181 available vs. actual, 181 prerequisites and, 204 PermissionSet element, 180 pipelines, 21 Pocket PC 2003, 10 PostBuild target, 131 PostBuildDependsOn property, 131 PostBuildEvent target, 32 PreBuildEvent target, 32 predefined targets, 32–37 predefined tasks, 25, 37 prerequisites, 201–216 building custom, 210–216 common ClickOnce scenarios and, 246–252
■INDEX
■Q QMake (Qtopia Build System), 23
■R ReadLinesFromFile task, 38 Rebuild target, 32 ReflectionPermission, 176 RegisterAssembly task, 38 RegistryPermission, 176 removable media, deploying ClickOnce applications from, 251 RemoveDir task, 38 RemoveDuplicates task, 38 Reports node (Visual Studio Team Explorer pane), 114 Required attribute, 41, 92 reserved properties, 26 ResGen target, 33 ResGen task, 38 ResolveAssemblyReference task, 38 ResolveComReference task, 38 ResolveKeySource task, 38 ResolveNativeReference task, 38 ResolveReferences target, 33 Run target, 32 RunAllTests target, 95, 102, 105
■S scalars, 91 scheduling team builds, 133 SCM (source control management) utilities, 107
security ClickOnce and, 154–159, 169–181 ClickOnce data directory and, 200 security certificates, 170 security policies, 174 SecurityPermission, 176 semicolon (;), multiple files and, 46 service-oriented architecture (SOA), 16 SGen task, 38 SharePoint Services, TFS and, 110 Shutdown method (ILogger), 78 side-by-side assemblies, 138 side-by-side deployment, 138 Signature element, 164 SignClickOnceDeployment target, 33 SignFile task, 38 SimpleFileLogger class, 78–80 smart clients, 2, 15, 46 deploying via ClickOnce, 190, 144–159 typical deployment for, 18 smart device applications, 2, 10 Smartphone 2003, 10 SOA (service-oriented architecture), 16 SocketPermission, 176 source control management (SCM) utilities, 107 Source Control node (Visual Studio Team Explorer pane), 114 SQL Server 2005, TFS and, 110 SqlClientPermission, 177 static analysis of permissions, 177 StorePermission, 177 subscription element, 163 System Checks tab (BMG tool), 224 targets, 23, 25, 27–37, 127 creating, 27 reusing, 63–69 Task class, 91 tasks, 23, 25, 37–42 creating, 38–42 UsingTask declaration for, 93 Team Build, 107–135 automating/scheduling, 133 error handling for, 131 extending, 127–131 generated files and, 124 how it works, 123–127 using, 117–123 Team Builds node (Visual Studio Team Explorer pane), 114 Team Foundation Build, 108–112 Team Foundation Build client, 109 Team Foundation Server (TFS), 107–110 team projects, in VSTS, 110 TeamBuild target, 131 TeamBuildDependsOn property, 131 test cases, 85–103
Find it faster at http://superindex.apress.com/
creating via BMG tool, 221–227 deploying via ClickOnce, 181–183 installing, 202 later application versions and, 250 presentation layer, 14 PrintingPermission, 176 product manifests (product.xml), 205–210 writing, 212 project files ASCII character codes for, 49–53 creating, 24 well-known metadata for, 46 properties, 25, 45 $() syntax for, 59 vs. environmental variables, 63 reusing, 64–69 property values, 59–62 PropertyGroup element, 25 public builds, 108 automating/scheduling, 133 Publish target, 33, 237 Publish Wizard, 183 publish.htm page, 165, 183 PublishOnly target, 33
257
258
■INDEX
Test target, 131 TestDependsOn property, 131 TFS (Team Foundation Server), 107–110 tfsbuild.exe utility, 133 TFSBuild.proj file, 124, 127, 133 TFSBuild.rsp file, 124 thick clients, 3, 15, 137 thin clients, 3, 14, 137 typical deployment for, 18 three-tier architecture, 15 tools, 219–252 BMG (Bootstrapper Manifest Generator), 219–227 build, 24–43 mage.exe, 228–236 mageui.exe, 147, 228, 232 make-style build, 22 source control management, 107 tfsbuild.exe, 133 VSTA/VSTO, 2 Touch task, 38 transforms, 61 troubleshooting loggers, 85 MSBuild errors and, 69–73 trust licenses, 154 trusted publishers, 154, 170 installing programmatically, 246–249 trusted sites, configuring via Internet Explorer, 219 trustInfo element, 164, 179 TrustUrlParameters property, 246 two-tier architecture, 15
■U UAB (updater application block), 141 UIPermission, 176 underscore (_) character, 69 unit tests, 85–106 UnregisterAssembly task, 38 update element, 163 update notification, 167 update policies, configuring, 167 UpdateCompleted event, 198 updater application block (UAB), 141 updating applications, 166–169 ClickOnce and, 151 UAB for, 141 URL parameters, passing to ClickOnce applications, 245 UsingTask declaration, 93
■V VB.NET, IntelliSense in the Zone feature and, 178 Vbc task, 38
VCBuild task, 38 VCOverrides.vsprops file, 124 vectors, 91 verbosity, 76, 83 Verbosity property (ILogger), 78 versioning, ClickOnce and, 151 Visual SourceSafe, 107 Visual Studio, 24, 54 Visual Studio 2005 ClickOnce and, 147 Debug in Zone feature in, 177 prerequisites and, 201 unit testing framework and, 85 Visual Studio Team System (VSTS), 107 Visual Studio Tools for Applications (VSTA), 2 Visual Studio Tools for the Microsoft Office System (VSTO), 2
■W Warning task, 38 Web applications, 2, 6 Web services, 2, 9 web sites Gotdotnet.com, 219 NUnit, 85 Visual Studio Team System, 107 web.config file, 6, 8 WebBrowserPermission, 176 WebPermission, 177 well-known metadata, 45–48, 57 whitespace, 51 wildcards, multiple files and, 46 Windows CE 5.0, 10 Windows Forms applications. See smart clients Windows FreeCell game, 76 Windows Installer, writing, 210 Windows services, 2, 11 Windows SharePoint Services, TFS and, 110 Windows XP, side-by-side deployment and, 138 Work Items node (Visual Studio Team Explorer pane), 113 WorkingWithPreReqs sample application, 202 WorkspaceMapping.xml file, 124 WriteLinesToFile task, 38, 103
■X XML Schema Document (XSD), 54 XmlLogger, 80–83