|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionPowershell scripts allow you to take advantage of .NET libraries and write scripts which are almost as powerful as the .NET code itself. You can do many powerful operations like call external DLLs, use .NET namespaces like Automating deploymentThe Powershell script does the following for you:
After the deployment script runs, all you need to do is extract the zip file on the server and that's all! You can easily FTP the modified files instead of copying only the zip file, by changing the FTP part at the end of the script. Configuring the scriptThe script takes some command line parameters: Param (
$CONFIG = "alpha", # Configuration setting.
# e.g. alpha, beta, release
$VERSION = 1, # Version number
# for the deployment. Increase as you will
[DateTime] $LAST_CHANGE_DATETIME =
[DateTime]::Now.AddDays(-10), # File change date to consider
$FROM ="SampleWeb", # Location of web site.
# Relative path to the script
$FTP = 1 ) # 1 - FTP to host. 0 - Don't FTP
The parameters are:
Then, define the FTP configuration in the following block: # FTP Configuration where the package is uploaded
$HOSTNAME = "ftp.pageflakes.com";
$UPLOAD_DIR = "updates"; # Folder where the final
# zip package is uploaded
$USER = "UserName"; # Enter a user name for FTP Server
$PASS = "Password"; # Enter a password for FTP Password
After that, some handy configurations: # Exlude the following folder while copying files $EXCLUDE_FOLDERS = @('\App_Data', '\temp'); $DEFAULT_ASPX = "Default.aspx"; # The aspx file where script tag # references are updated with version no $SCRIPT_FILES = @('Script.js'); # Script files which gets # a version suffix added $ASSEMBLIES_TO_REMOVE = @('Microsoft.Build.Framework'); # Unwanted assemblies inside <assemblies> You can define which folders to exclude always in the deployment. For example, no need to deploy App_Data. Also define the ASPX file name which gets modified with new script references automatically. The
After that, you define the configurations for different deployment targets: if( $CONFIG -eq "alpha" )
{
$DB_HOST = "(local)";
$DB_NAME = "AlphaDatabase";
$CREDENTIAL = "trusted_connection=false;";
}
if( $CONFIG -eq "beta" )
{
$DB_HOST = "(local)";
$DB_NAME = "BetaDatabase";
$CREDENTIAL = "trusted_connection=false;";
}
if( $CONFIG -eq "release" )
{
$DB_HOST = "(local)";
$DB_NAME = "ReleaseDatabase";
$CREDENTIAL = "trusted_connection=false;";
}
Here, I have specified different connection strings for different deployment targets. Copying only modified filesThe script first gets all the modified files: # Get the files which were changed # after the $LAST_CHANGE_DATETIME $changedFiles = get-childitem $srcPath -exclude "*.log" -Recurse | where { $_ -is [System.IO.FileInfo] -and $_.LastWriteTime -ge $LAST_CHANGE_DATETIME -or $_.Name -eq $DEFAULT_ASPX -or $_.Name -eq "web.config" } The query finds all the files modified after the specified change date. But it always includes Default.aspx and web.config as these two files are always deployed. This is a requirement in Pageflakes and that's why I have put it there. You can remove it if you don't want it. The next step is to decide the relative path for each file inside the deployment folder and copy the files from the source folder to the destination folder: foreach( $file in $changedFiles )
{
[string]$filePath = $file.ToString();
[string]$relativePath =
$file.DirectoryName.substring($srcPath.Length);
# if the file is in one of the excluded folders, don't copy
$canCopy = 1;
foreach( $excludeFolder in $EXCLUDE_FOLDERS )
{
if( $relativePath.StartsWith($excludeFolder) )
{
$canCopy = 0;
}
}
Before copying a file, it checks if the relative path contains any of the excluded folders. If the folder is valid, copy the file. It also ensures the directory structure is created before copying the file: if( $canCopy -eq 1 )
{
# if the relative path contains a subdirectory,
# then create a subdirectory under the deploy path
[string]$copyPath = [System.IO.Path]::Combine(
$deployPath, $relativePath.TrimStart('\') );
if( ![System.IO.Directory]::Exists($copyPath) )
{
$newDir =
[System.IO.Directory]::CreateDirectory($copyPath);
}
copy $filePath $copyPath;
}
The next step is to configure the web.config according to the deployment configuration. For example, configuring the connection string for production server. Configure web.config automaticallyFirst, it loads the web.config in $webConfigFilePath =
[System.IO.Path]::Combine( $deployPath, "web.config" );
if( [System.IO.File]::Exists( $webConfigFilePath ) )
{
" Web config found";
[System.Xml.XmlDocument]$doc =
new-object System.Xml.XmlDocument;
$doc.Load($webConfigFilePath);
Then, it changes some settings like changing the compilation mode to $root = $doc.get_DocumentElement();
# Change compilation mode to debug="false"
$root."system.web".compilation.debug = "false";
# Set deployment configuration specific connection string
$root.connectionStrings.add.connectionString = $CONNECTION_STRING
You can set multiple connection strings here easily. Then it runs through all foreach( $item in $root.appSettings.add )
{
if( $item.key -eq "Proxy" )
{ $item.value = ""; }
if( $item.key -eq "Version" )
{ $item.value = "" + $VERSION; }
}
The next step is to remove unwanted assemblies which should not be deployed to servers. foreach( $item in $root."system.web".compilation.assemblies.add )
{
foreach( $assemblyName in $ASSEMBLIES_TO_REMOVE )
{
if( $item.assembly.Contains($assemblyName) )
{
$removedNode =
$root."system.web".compilation.assemblies.RemoveChild( $item );
}
}
}
This gives us a nicely prepared web.config. I bet you have been doing all these manually before uploading some fixes to server or making new releases. See how Powershell can completely automate this and remove the possibility of human errors completely. Update .js file names and <script> tagsThe next step is to update all JavaScript references. In Pageflakes, we have a problem that when we upload modified JavaScript's, they do not get downloaded by existing users because they are cached in the client's browser. So, we need to change the file name every time we make changes to those scripts and upload on servers. This is error prone manual work. After changing the file name, we need to go to Default.aspx and change all script references and put the new file name. This is more manual work, and we generally make serious mistakes and screw up production servers at least once every three deployments. So, we completely automated this process using this Powershell script, and now we never worry about the script file problem at all. foreach( $scriptFile in $SCRIPT_FILES )
{
updateReferenceWithNewVersion $scriptFile
$DEFAULT_ASPX $VERSION $srcPath $deployPath
}
The actual work is done inside the function where we look for the file name and replace with the new name. function updateReferenceWithNewVersion( $fileName,
$referenceFile, $versionNo, $srcPath, $destPath )
{
$filePath = [System.IO.Path]::Combine( $destPath, $fileName );
if( [System.IO.File]::Exists( $filePath ) )
{
"$fileName exists. Upgrading its version in $referenceFile"
$referencePath =
[System.IO.Path]::Combine( $destPath, $referenceFile );
if( -not [System.IO.File]::Exists( $referencePath ) )
{
" Copying $referenceFile because it's not updated"
$referenceSrcPath =
[System.IO.Path]::Combine( $srcPath, $referenceFile );
$result = copy $referenceSrcPath $referencePath
}
$newFileName = $fileName.Split('.')[0] + "-" +
$versionNo + "." + $fileName.Split('.')[1];
ren $filePath $newFileName
$referenceContent =
[System.IO.File]::ReadAllText( $referencePath );
$referenceContent =
$referenceContent.Replace( $fileName, $newFileName );
[System.IO.File]::WriteAllText(
$referencePath, $referenceContent );
}
else
{
"$fileName not available"
}
}
JSMIN all script filesWe use JSMIN to compress all JavaScript files before deploying. This allows us to keep uncompressed JS files which contain lots of comments and spaces inside them without any reservation. When a script file gets deployed, JSMIN removes all comments and spaces from it, and generates a very compact version of the script which browsers understand without any problem. This reduces script download time significantly, and improves the overall site download speed. $jsFiles = get-childitem $deployPath
-include *.js -Recurse | where { $_ -is [System.IO.FileInfo] }
$p = new-object System.Diagnostics.Process;
$si = new-object System.Diagnostics.ProcessStartInfo;
$si.FileName = $jsminPath;
$si.CreateNoWindow =
The idea is to launch jsmin.exe using the The attached source code with the article contains a special version of JSMIN which does not remove line breaks. We collect JavaScript errors from client side. So, we need to know the line number and the code on that line. When JSMIN removes the line number, it becomes impossible to trace the error. This is why we have modified JSMIN and prevented it from removing line breaks.
| ||||||||||||||||||||||