Showing posts with label powershell. Show all posts
Showing posts with label powershell. Show all posts

Wednesday, August 17, 2022

Find which server an Azure Devops build has been run on


We have Azure Devops Services coupled with several internal build servers, so when troubleshooting builds, especially sporadic issues, it helps to know which build server the build has run on in order to understand if it is a server specific issue or not. We happen to use Azure Devops Services for our Azure Devops installation and internal build servers.

The powershell script below uses the Azure Devops rest api to retrieve the build information. You may need to adjust the proxy parameter for the invoke-restmethod lines as well as update some of the variables to match your environment. 

The list of build runs i shown in the powershell gridview. There is a ShowInExcel commandline switch in case you prefer to view data with Excel. With this switch enabled, the data will be shown in a gridview and then saved as a csv file, finally the invoke-item command is used to trigger the opening of the .csv file with the assiocated application. Excel may be overkill in this situation but I left it as an example.


[CmdletBinding()]
param(
[System.String]$project="---- Default AZDOS PROJECT ----",
[System.String]$buildIdNumber="--- Default BUILD DEFINITION ID ---",
[System.String]$maxBuilds="10",
[switch]$ShowInExcel
)
$pat = '----- YOUR AZDOS PAT TOKEN -----'
$header = @{Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$($pat)"))}
$baseBuildUrl = "https://dev.azure.com/----- COLLECTION ----/$project/_apis/build/builds"
$buildListUrl = "$($baseBuildUrl)?api-version=6.0&definitions=$buildIdNumber&queryOrder=finishTimeDescending&maxBuildsPerDefinition=$maxBuilds"
Write-output $buildListUrl
try
{
#Get build data
$builds=Invoke-RestMethod -uri $buildListURL -Method Get -Header $header -Proxy $env:HTTP_PROXY #-OutFile $outFileName
$builds.value[0].buildNumber
$List = New-Object System.Collections.ArrayList
foreach ($build in $builds.value)
{
#$build=$builds.value[2]
write-host $build.status
if ($build.status -ne "notStarted"){
$timeLineUrl="$baseBuildUrl/$($build.id)/Timeline?api-version=2.0"
$timeline=Invoke-RestMethod -uri $timeLineUrl -Method get -Header $header -Proxy $env:HTTP_PROXY
# Can't get the duration if the build isn't done!
if ($build.status -ne "completed"){
$duration = $build.status
} else {
$duration = "{0:hh}:{0:mm}:{0:ss}" -f $(New-TimeSpan -end $(get-date $build.finishTime) -start $(get-date $build.startTime))
}
$Hash = [ordered]@{
date = get-date -date $build.startTime -Format "yyyy/MM/dd"
time = get-date -date $build.startTime -Format "HH:mm"
duration = $duration
agent = $timeline.records[2].workerName #arbitrary choice of a step in the collection
result = $build.result
successfulSteps = ($timeline.records.result -eq "succeeded").count
buildId = $build.id
buildNumber = $build.buildNumber
branch = $build.sourceBranch.Replace("refs/heads/","") # still some builds don't use branch name as build number
}
$List.Add( $([pscustomobject]$Hash) )
}
}
if ($ShowInExcel) {
$file="$($env:TEMP)\$($build.definition.name)-builds-$(get-date -Format "yyMMdd.HHmm").csv"
# Show results in a gridview window and then save to a temporary file
$List | Out-GridView -PassThru -Title "$($build.definition.name) builds" | Export-Csv -NoType -UseCulture -path $file
write-host "Data saved in $file."
write-host "Opening in Excel"
invoke-item $file
}
else {
$List | Out-GridView -Title "$($build.definition.name) builds"
}
}
catch [Exception]
{
Write-Host $_.Exception.Message
Write-Host $_.Exception.Response
}







Analyze Azure Devops agent usage

Azure Devops agents write logs of all the work they do: with one log per pipeline run. These logs can be analyzed to understand the usage levels of agents on the server by using both the file attributes and contents, which include all the variables and script for a process.

Update the powershell script below with the corresponding folders for your agent installation and update the list of agents, or remove the foreach if you only have one agent running. 

The log folders don't appear to be cleaned so you might want to implement a cleaning schedule, or limit the get-childItem to the most recent week or month if you have a lot of traffic on your agents.

$List = New-Object System.Collections.ArrayList foreach ($agent in ("A7","B1","C1")) { $expression = /^.(\d{4}-\d{2}-\d{2}) .*Job message:\n(.*)\[\1 $WorkerFiles=Get-ChildItem C:\azagent\$agent\_diag\Worker*.log foreach ($file in $WorkerFiles) { $contents = ( Get-Content $file -raw )-replace ' \*{3}', ' "BLOCKED"' #$contents = ( Get-Content C:\azagent\B1\_diag\Worker_20210706-104057-utc.log -raw )-replace ' \*{3}', ' "BLOCKED"' $result = $contents -match "(?s)\[(\d{4}-\d{2}-\d{2}).* Job message:(.*?)\[\1" $jobjson = ConvertFrom-Json $Matches[2] if ($result) { $Hash = [ordered]@{ file=$file.Name date = get-date -date $file.CreationTime -Format "yy/MM/dd" start = get-date -date $file.CreationTime -Format "HH:mm" end = get-date -date $file.LastWriteTime -Format "HH:mm" duration = $file.LastWriteTime - $file.CreationTime environment = $jobjson.variables.'release.environmentName'.value agent = $agent project = $jobjson.variables.'release.definitionName'.value link = $jobjson.plan.owner._links.web.href } $List.Add( $([pscustomobject]$Hash) ) } else { write-host "No Match for " + $file.FullName} } } $fileName="$($env:TEMP)\TFSAgentUsage.csv" $List | Export-Csv -NoType -UseCulture -path $fileName

Wednesday, June 15, 2022

Powershell snippets for Octopus Deploy

Extract url to triggering build from an Octopus Deploy release

Our build pipelines create releases in Octopus Deploy and send along the commit information. It is possible to find the buildid and url to the build from the Octopus.Release.Notes variable.


if ($OctopusParameters["Octopus.Release.Notes"] -match "Build \[(.+)\]\((?<url>.+)\)") 

{

   $buildUrl = $Matches.url

   $buildId=($buildUrl -split "=")[-1]

} else {

   $buildUrl="Build info missing. Manually created release?"

   $buildId ="Unkown"

}


Octopus.Deployment.Changes contains change information and a markdown version of that information is in  Octopus.Deployment.ChangesMarkdown.


Parameter validation in step templates

Validate parameters in step template and create variables from the parameters.Inside the foreach, the first line checks 'required' parameters have a value.
The second if statetment checks for '#{' in the parameter value.
The last line creates a variable with the name of the parameter and sets the value.
{
    # Check for required variables
    if ( @("pShareName","pPhysicalPath") -contains $pName) { if (!$OctopusParameters[$pName]) {Write-Warning "Parameter $pName cannot be empty!"; exit 1}}
    # Check for #{ in any variables
    if ($OctopusParameters[$pName] -and $OctopusParameters[$pName].indexOf("#{") -ne -1) { Write-Warning $("Parameter {0} contains '{1}'! Check variable exists and is scoped properly." -f $pName,$OctopusParameters[$pName]); exit 1 }
    # set a local variable
    Set-Variable -Name $pName -Value $OctopusParameters[$pName]
}

Wednesday, May 11, 2022

Copy variables from one Octopus Deploy process to another

Cloning Octopus Deploy steps from one process to another does not copy any variables between the two, for understandable reasons, and there is no inbuilt method for cloning variables.

This powershell script:

  • gets the project variables from an Octopus Deploy process
  • presents the variables in a Powershell gridview
  • adds the variables selected in the gridview to the target Octopus Deploy process - there is no logic checking if the variables already exist

$sourceProjectName="API.SourceProject"
$targetProjectName="API.TargetProject"

# OD API KEY
$ODAPIKey = "API-PUT-YOUR-KEY-HERE"
$ODUrl = "http://od.somecompany.org"

$credential = "?apikey=$ODAPIKey"

# for all projects
$ODProjectQuery = "$ODUrl/api/projects/all$credential"

$headers = @{
 "X-Octopus-ApiKey"="$ODAPIKey"
 "accept"="application/json"
}

function putData ($link, $body)
{
    $QueryString = "{0}{1}" -f $ODUrl, $link
    #UTF-8 conversion is required to handle international letters like ö å ñ
    $body_utf8=([System.Text.Encoding]::UTF8.GetBytes($($body | ConvertTo-Json -Depth 15)))
    $requestResponse=Invoke-WebRequest -uri $QueryString -Method Put -Body $body_utf8 -ContentType "application/json" -Headers $headers
    Write-Host "Update Status: $($requestResponse.StatusCode) $($requestResponse.StatusDescription)"
}

function getData ($link)
{
    # Create querystring from partial link
    $QueryString = "{0}{1}{2}" -f $ODUrl, $link, $credential
    Invoke-RestMethod -uri $QueryString -Method Get
}

try {

    #Get a list of all projects
    $projects = Invoke-RestMethod -uri $ODProjectQuery -Method get
       
    # Select Source Project
    $sourceProject=$projects | Where-Object { $_.Name -eq $sourceProjectName}

    # Get variables
    $sourceVars=getData $sourceProject.Links.Variables

    # Display variables in gridview and save selected variables
    $importVars = $sourceVars.Variables | Select-Object -Property * -ExcludeProperty Id | Out-GridView -PassThru -Title "Select variables to copy to target project"
    # write out selected variables to output
    $importVars | ConvertTo-Json    

    # Get Target project
    $targetProject=$projects | Where-Object { $_.Name -eq $targetProjectName}
   
    # get target variables
    $targetVariables = getData $targetProject.Links.Variables

    Write-Host "Target variable version pre-update: $($targetVariables.Version)"

    # Add selected variables to target variables
    $targetVariables.Variables += $importVars

    # Send updated variable list back to target OD process
    putData $targetProject.Links.Variables $targetVariables
}
catch
{
    Write-Host $_.Exception.Message
    Write-Host $_.Exception.Response.StatusDescription
    Write-Host $_.ErrorDetails
}

Monday, April 24, 2017

Using outlook rules, json and powershell to link TFS 2017 work items to a parent

We use a TFS user story as a Support inbox and wanted to simplify creating new work items for common issues. We created templates for work items based on existing work items which had the parent required but discovered that the parent link does not get created for the new items.

Since our team doesn't have admin access to the TFS server, I decided to see if I could use the TFS notifications to trigger a custom outlook rule script which would send the task id to a local powershell script which in turn does the actual link creation. I decided that would be easier than trying to work out how to access REST services through VBA since there are more powershell examples out there than VBA examples.

I chose to go with json to define the parent link instead of doing it through code. It seemed simpler to me for maintenance purposes. The relevant json documentation was just a little confusing for a json novice because it looks like there should be double curly brackets under attributes. However doing that gave me the error:
"You must pass a valid patch document in the body of the request."

The final json looks like this
[
  {
    "op": "add",
    "path": "/relations/-",
    "value":
    {
        "rel": "System.LinkTypes.Hierarchy-Reverse",
        "url": "https://tfs.myCompany.org/tfs/DefaultCollection/_apis/wit/workitems/259355",
        "attributes":
        {
            "isLocked": false 
        }
    }
}
]
The Outlook rule looks for all emails with "Task" in the subject and "create Task" in the body.

You may need to enable the developer tools in outlook first. Once it is enabled, click on the Developer Tab and click on Visual Basic to the far left. This will open the VBA editor.

The outlook rule script itself is very simple.
Sub SendToTFS(MyMail As MailItem)
 If InStr(1, MyMail.Subject, "Task") = 1 Then
   Dim taskid As String
   taskid = Mid(MyMail.Subject, 6, 6)
   scriptCmd = "powershell.exe -NoLogo -NonInteractive -File ""e:\scripts\TFSLinkParent.ps1"" -argumentlist " & taskid & " > ""e:\scripts\TFSLinkParentLog.txt"""
   Shell scriptCmd
 End If
End Sub
The code first checks that Task is right at the beginning of the subject line to avoid reacting to forwards and replies.
The task id is then extracted from the subject line and sent to the powershell script.

Finally the Powershell script to do the job looks like this:
$TaskId = $args[1]

$taskItemURL = "https://tfs.mycompany.org/tfs/DefaultCollection/_apis/wit/workitems/$TaskId"
$taskItemRequest = $taskItemUrl+'?$expand=relations' 
$taskItemJson = Invoke-RestMethod -uri "$taskItemRequest" -Method get -UseDefaultCredentials -OutFile E:\scripts\TFSLinkReqLog.txt

if(!($taskItemJson.relations))
{
    $result = Invoke-RestMethod -uri $taskItemURL"?api-version=1.0" -Method patch -UseDefaultCredentials -ContentType application/json-patch+json -InFile E:\scripts\JsonTemplate.txt  -OutFile E:\scripts\TFSLinkLog.txt
}

The script starts with downloading the json for the work item and checks that it doesn't already have any relations. Child relations would also get caught by this check. If there aren't any relations then the current work item is attached to the parent.

Tuesday, November 15, 2016

Update all binding thumbprints

We have 20+ applications and have to update to a new certificate. To avoid having to do a new build and release of all of these applications, some of which haven't been updated for some time, I chose to create a powershell script to update all send ports on the fly.

The script does not stop or start host instances. This could easily be incorporated; check my other blog entry on starting and stopging host instances.

The script uses the BizTalk ExplorerOM to access the settings which means nothing extra needs to be installed on the BizTalk servers.

This script looks long because it includes so much confirmation in the way of output for testing before the final run. The real logic is only 8 lines, including 4 lines of variable declarations.

This example changes two thumbprints at once. It could easily be modified up or down.

$oldClientCert = "ee aa bb 11 22 33 44 55 66 77 88 99 00 ff dd cc ab cd ef 01"
$newClientCert = "ne wt hu mb pr in tg oe si nh er e0 00 00 00 00 00 00 00 00"
$oldServiceCert = "aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 12 23 34 56"
$newServiceCert = "34 2a 15 53 3e 7d 6a 0c 51 20 e4 50 6b 53 df 72 84 55 aa 6a"
  
[void] [System.reflection.Assembly]::LoadWithPartialName("Microsoft.BizTalk.ExplorerOM")  
$Catalog = New-Object Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer  
$Catalog.ConnectionString = "SERVER=DBINSTANCENAME;DATABASE=BizTalkMgmtDb;Integrated Security=SSPI"

#EnumerateSendPorts $Catalog  
 $port = $catalog.SendPorts[1]
 Write-host "B4 ----> " $port.PrimaryTransport.TransportTypeData

 $catalog.SendPorts | % {

# Line below replaces thumbprints  - UPDATES ORIGINAL VALUE - BUT NO SAVE
$_.PrimaryTransport.TransportTypeData=_
($_.PrimaryTransport.TransportTypeData.Replace($oldServiceCert,$newServiceCert)).Replace($oldClientCert,$newClientCert);

# Line below replaces thumbprints and prints out the new TransportTypeData string - NO UPDATE TO ORIGINAL VALUE
#($_.PrimaryTransport.TransportTypeData.Replace($oldServiceCert,$newServiceCert)).Replace($oldClientCert,$newClientCert);
   }
$port = $catalog.SendPorts[1] 
Write-host "After ----> " $port.PrimaryTransport.TransportTypeData

#No changes are saved until the following line is run
$Catalog.SaveChanges(); 

When testing comment out the the last line to skip saving the updates. To just output the updates, comment out the row updating the original value and uncomment the No Update line.

Don't forget to change the connection string to point to the correct management database instance!

Blog software may force some line breaks - and I added one underscore (_) to indicate I broke the line there.

Thursday, June 25, 2015

BizTalk - comparing bindings files with excel source

We dynamically generate BizTalk bindings during in our build process. The process uses a binding file and replaces urls and thumbprints with values from a an excel spreadsheet.

The process for updating both of these files is manual so it is natural to find they get out of synch. Manually browsing a binding file is not an easy task so I wrote a powershell script that extracts and compares portnames between the two files.

In our case only the sendports get dynamically updated.
First extract the portnames from the binding file:
  $BindingFileName ="D:\bindings\App1~Binding~Template.xml"
  if ( -not (Test-Path -path $BindingFileName) ){
      "Can't Find"
      $BindingFileName
 exit
 } 
# Load binding information from file
$Bindings = [xml](get-content $BindingFileName)

# extract the ports
$BindingPorts = select-xml $Bindings -xpath "//SendPort" 

# get just the port names
$BindingPortNames = $BindingPorts | foreach { $_.node.Name } | sort-object –Unique 
Now that I have a sorted list of ports from the binding file, I need to get the ports from the excel file. Excel does not need to be installed on the server but the Excel drivers for oledb do need to be installed. Note: This driver is a 32 bit driver. use the -runas parameter or run in the x86 ISE.
$ExcelFileName = "D:\excel\App1~PortConfig.xls"
 
$OleDbConn = New-Object "System.Data.OleDb.OleDbConnection"
$OleDbCmd = New-Object "System.Data.OleDb.OleDbCommand"
$OleDbAdapter = New-Object "System.Data.OleDb.OleDbDataAdapter"
$DataTable = New-Object "System.Data.DataTable"

$OleDbConn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=""$ExcelFileName"";Extended Properties=""Excel 12.0 Xml;HDR=YES"";"
$OleDbConn.Open()

$OleDbCmd.Connection = $OleDbConn
$OleDbCmd.commandtext = "Select Port from [SENDPORT_WCF$]"
$OleDbAdapter.SelectCommand = $OleDbCmd

$RowsReturned = $OleDbAdapter.Fill($DataTable)
$OleDbConn.Close()

#Output something so user sees something is happening 
ForEach ($row in $DataTable) {
Write-host $row.Port
}

$ExcelPortNames = $DataTable | foreach {$_.Port} | sort-object –Unique

The excel file has different tabs for different send ports. This line:
$OleDbCmd.commandtext = "Select Port from [SENDPORT_WCF$]"
specifies which column (Port) to select from which sheet [SENDPORT_WCF$]. 

The final step is to compare the two lists of portnames.
Compare-Object $ExcelPortNames $BindingPortNames -includeequal | ft inputobject, @{n="file";e={ if ($_.SideIndicator -eq '=>') { "binding" } else {if ($_.SideIndicator -eq '<=')  { "excel" } else {"=="}} }} | Out-File "d:\temp\Bank 2.0 bindings comparison.txt" 
Note that this is one long line that has been wrapped by the blog software.
The final result is a table that looks like this:
InputObject         file   
-----------         ----   
WcfSendPort_app1 ==     
WcfSendPort_app2 ==     
WcfSendPort_app3 ==     
WcfSendPort_appx binding
WcfSendPort_appy binding
WcfSendPort_appz binding
WcfSendPort_app1z excel  
WcfSendPort_app2z excel  

I'll leave it as an exercise for the reader to process a whole directory full of bindings files. :). One could also add parameters and send the file names on the comand line.

The complete code:


#Param(
#  [string]$BindingFileName
#)

#Binding files
  $BindingFileName ="D:\bindings\App1~Binding~Template.xml"
  if ( -not (Test-Path -path $BindingFileName) ){
      "Can't Find"
      $BindingFileName
 exit
 } 
 
# Load binding information from file
$Bindings = [xml](get-content $BindingFileName)

# extract the ports
$BindingPorts = select-xml $Bindings -xpath "//SendPort" 

# get just the port names
$BindingPortNames = $BindingPorts | foreach { $_.node.Name } | sort-object –Unique 
 
#EXCEL 
 
$ExcelFileName = "D:\excel\App1~PortConfig.xls"
 
$OleDbConn = New-Object "System.Data.OleDb.OleDbConnection"
$OleDbCmd = New-Object "System.Data.OleDb.OleDbCommand"
$OleDbAdapter = New-Object "System.Data.OleDb.OleDbDataAdapter"
$DataTable = New-Object "System.Data.DataTable"

$OleDbConn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=""$ExcelFileName"";Extended Properties=""Excel 12.0 Xml;HDR=YES"";"
$OleDbConn.Open()

$OleDbCmd.Connection = $OleDbConn
$OleDbCmd.commandtext = "Select Port from [SENDPORT_WCF$]"
$OleDbAdapter.SelectCommand = $OleDbCmd

$RowsReturned = $OleDbAdapter.Fill($DataTable)
$OleDbConn.Close()

#Output something so user sees something is happening 
ForEach ($row in $DataTable) {
Write-host $row.Port
}

$ExcelPortNames = $DataTable | foreach {$_.Port} | sort-object –Unique

#COMPARISON

Compare-Object $ExcelPortNames $BindingPortNames -includeequal | ft inputobject, @{n="file";e={ if ($_.SideIndicator -eq '=>') { "binding" } else {if ($_.SideIndicator -eq '<=')  { "excel" } else {"=="}} }} | Out-File "d:\temp\App1_bindings_comparison.txt" 

Wednesday, November 13, 2013

BizTalk Admin: Powershell script to save suspended messages and terminate suspended instances.

My latest BizTalk administration powershell scripting mission has been to save suspended messages and terminate all suspended service instances. WMI exposes both message instances and service instances and corresponding methods.
Visual Studio Servier Explorer showing WMI BizTalk Classes

The following script is in two steps. First all suspended messages are saved to file and then all suspended service instances are terminated. This script is used as part of a deployment process when the BizTalk cluster is not taking any messages. If the server is actively processing messages, a message could get suspended between the two steps and not get saved but the owning service instance could be terminated.

I have been thinking that one could use the serviceinstanceid to connect directly to the service instance from the message instance and terminate it then thereby avoiding the possibility of deleting a service instance without its corresponding message being saved. However, routing failure reports cannot be saved, (attempting to save them causes an exception) so the routing failure messages and service instances would still need to be cleaned up.
The simplest form of this script looks like this(note that blog software wraps the code):
$nameSpace ="root\MicrosoftBizTalkServer"
$path = "c:\messagedump" 

$filter="(ServiceClass=1 OR ServiceClass=4) AND (ServiceInstanceStatus=4 OR ServiceInstanceStatus=32)"
get-wmiobject MSBTS_MessageInstance -namespace $nameSpace -filter $filter | invoke-wmiMethod -name SaveToFile -arg $path > $null

$filter="(ServiceStatus=4 OR ServiceStatus=32)"
get-wmiobject MSBTS_ServiceInstance -namespace $nameSpace -filter $filter | invoke-wmiMethod -name Terminate > $null
The first filter specifies orchestration or messaging service classes that are suspended(resumable) or suspended(not resumable). All message instance meeting this criteria get returned by the call to get-wmiobject and are piped directly into invoke-wmiMethod. The SaveToFile method exposed by WMI has a path parameter specifying where messages should be written.

Invoke-wmiMethod returns information that is not particularly meaningful in this context so it gets piped out to $null to minimize clutter.

The second filter specifies all resumable and non-resumable service instances and is used to select the service instances. Again the results are piped directly to Invoke-wmiMethod which will invoke the Terminate method. The terminate method has no parameters.

For further reference: here is the MSDN documentatation for the Service Instance Status codes http://msdn.microsoft.com/en-us/library/ee268242(v=bts.10).aspx and for Message Instance Service Class http://msdn.microsoft.com/en-US/library/ee253972(v=bts.10).aspx

In the dev and test environments we don't normally want to save the suspended messages so I extended the script to accept parameters for flexibility. I also had some exceptions during development so I added a function to handle each message with a try/catch. The exception information will be printed but does not stop the processing. This would allow for eventually finetuning the catches if I wanted to account for specific errors.

The longer version of the script looks like this(again beware of line wrapping):
# define parameters: $path is where messages will be saved
# $save - a switch to trigger saving of suspended messages
# $purge - a switch to trigger purging suspended service instances

param([string] $path, [switch] $save, [switch] $purge)
$nameSpace ="root\MicrosoftBizTalkServer"
 
function Save-Message ($messageInstance)
{
  try
  {
    $messageInstance.SaveToFile($path) > $null
  }
  catch
  {
    $messageInstance.MessageInstanceId
    $_.Exception.GetType().FullName
    $_.Exception
  }
}
 
If ($save) {
  if (! $path) { $path = ".\messages"; }
  if (! (Test-Path $path)) { mkdir $path }

  $filter="(ServiceClass=1 OR ServiceClass=4) AND (ServiceInstanceStatus=4 OR ServiceInstanceStatus=32)"
   
  get-wmiobject MSBTS_MessageInstance -namespace $nameSpace -filter $filter | %{Save-Message($_)}
}
   
if ($purge) {
  $filter="(ServiceStatus=4 OR ServiceStatus=32)"
  get-wmiobject MSBTS_ServiceInstance -namespace $nameSpace -filter $filter | invoke-wmiMethod -name Terminate > $null
}
The main changes here are that the path becomes a parameter to the script. If the path is not specified then a default path is used. If the path doesn't exist it will be created. I also added a save and a purge switch to allow complete flexibility around saving and terminating.

I also call the SaveToFile method directly on the message object instead of using invoke-wmiMethod. In case you are new to powershell, the % operator used before the call to Save-Message is an alias for for-each.

Friday, October 18, 2013

Powershell to create BizTalk applications with references

The latest script to undergo migration to powershell is my application creation script. Our applications have references to each other so this script takes command line parameters for the application name and the references. If the application already exists the references will be added.

As usual for my latest blog entries, this script uses WMI and the BizTalk ExplorerOM. I also have minimized the error handling in order make the code easier to read.

Watch out for line wrapping from the blog software!
# define parameters: $app is application name
# $ref is a comma delimited string of references to create "Common,Schemas"
param([string] $app, [string[]] $ref)
 
 # Get local BizTalk DBName and DB Server from WMI
 $btsSettings = get-wmiobject MSBTS_GroupSetting -namespace 'root\MicrosoftBizTalkServer'
 $dbInstance = $btsSettings.MgmtDbServerName
 $dbName = $btsSettings.MgmtDbName
 
 # Load BizTalk ExplorerOM
 [void] [System.reflection.Assembly]::LoadWithPartialName("Microsoft.BizTalk.ExplorerOM")
 $BizTalkOM = New-Object Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer
 $BizTalkOM.ConnectionString = "SERVER=$dbInstance;DATABASE=$dbName;Integrated Security=SSPI"

#check incoming parameter for application name exists
 if (! $app)
{ 'Syntax is: Add_References -app "NewAppName" -ref Common,Schemas'; exit}

#create the application if it doesn't exist already 
if ($BizTalkOM.Applications[$app] -eq $null)
{
   $result =( btstask addapp /application:$app)
   "btstask result:" + $result
   $BizTalkOM.Refresh()
}

#add application references
foreach ($reference in $ref)
{
   "adding reference to " + $reference
   $BizTalkOM.Applications[$app].AddReference($BizTalkOM.Applications[$reference])
}

# Commit changes
$BizTalkOM.SaveChanges()  

I couldn't find a way to add an application without using btstask. Let me know if you know of a way, without using the Powershell Provider.

Thursday, October 10, 2013

Powershell to read the assembly comments field.

After migrating our applications from a Windows Server 2003 environment to a Windows Server 2008 environment we have found that the comments field of our assemblies is no longer visible in Windows Explorer.

After spending some time searching the internet for a simple PowerShell command I gave up and started experimenting in the PowerShell GUI.

This is the end result of my experiments:
((get-itemproperty .\serviceContract.dll ).VersionInfo).Comments

To list all of the custom attributes I used the following:
(get-itemproperty .\serviceContract.dll).VersionInfo | format-list

When things are busy it can can feel awkward to have to open up a command or powershell window, copy the name of the dll I want to check from another server, and then run a command. To make it simpler to check the version info I wrapped a batch file around my script and put it on my desktop so I could just drag and drop a .dll onto the batch file. My batch file looks like this:
@echo off
powershell e:\scripts\getVersionInfo.ps1 -dll '%1'
pause
Dropping a file on the batch file causes the comand window to open and run the batch with the file name as the first parameter and close again. I added the 'pause' to prevent the window from closing until I had the time to see the result.

Dragging a file onto the batch file causes the OS to try to start the batch file up with the working directory of the file being dropped. When the file comes from another server this will cause an error message. The version info appears too but it didn't feel like a tool I'd want to share.

To avoid the error message I created a shortcut to my batch file and configured the short cut's working directory property to start up in my script folder. Then I put the batch file and the powershell file into my script directory so I just have the shortcut on my desktop.

This dialog shows how to configure the shortcut.



It is possible to change the size of the font and window that pop up using the font and layout tabs.

To make it easier to spot my shortcut on my desktop, I clicked on the Change Icon button and chose an icon from the selection that appeared.



Now I just drag a file from windows explorer onto the info icon on my desktop and presto I have the comments data from my assembly.

I slightly modified my powershell script to accept the assembly name as a command line parameter when I added the batch script abstraction. My final powershell script looks like this:
param([string]$dll)
$dll + " version:"
((get-itemproperty "$dll").VersionInfo).Comments


The first line defines the command line parameter. The second line will just print out the name of the dll and the last line prints out the value in the comments field.
The final result is a lot simpler than all of the C# code I surfed past while looking for this solution, codewise and maintainablity wise.

Friday, October 4, 2013

Powershell scripts to stop/start BizTalk Applications

We are finally almost finished with a migration project from BizTalk 2006 to BizTalk 2010. Some of my 'tools' need updating. First up is my application start/stop utility since this is the only one that flat out doesn't work.

Out of curiosity I wanted to test different methods of accessing the BizTalk applications. WMI was automatically out because it doesn't expose BizTalk applications. My options were using the BizTalk Explorer OM which is part of the BizTalk installation or the BizTalkFactory Powershell Provider on CodePlex which requires an installation. It is pretty common for production environments to have strict limitations on what third-party code can be installed so the BizTalkFactory route is not always an option.

This first example uses the BizTalk Explorer OM. Beware of line wrapping from the blog software.

# declare -stop -start switch parameters
param([switch] $start, [switch] $stop)
 
 # Get local BizTalk DBName and DB Server from WMI
 $btsSettings = get-wmiobject MSBTS_GroupSetting -namespace 'root\MicrosoftBizTalkServer'
 $dbInstance = $btsSettings.MgmtDbServerName
 $dbName = $btsSettings.MgmtDbName
 
 # Load BizTalk ExplorerOM
 [void] [System.reflection.Assembly]::LoadWithPartialName("Microsoft.BizTalk.ExplorerOM")
 $BizTalkOM = New-Object Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer
 $BizTalkOM.ConnectionString = "SERVER=$dbInstance;DATABASE=$dbName;Integrated Security=SSPI"


 if ($stop)
 {
   $BizTalkOM.Applications | where-object{$_.status -eq "started"}  | ForEach-Object{ $_.stop("StopAll")}
   $BizTalkOM.SaveChanges()  
 } 
 if ($start)
 {
   $BizTalkOM.Applications | where-object{$_.status -eq "stopped"}  | ForEach-Object{ $_.start("StartAll")}
   $BizTalkOM.SaveChanges()  
 }
 
The very first line sets up parameters so this script can be called with a -start or -stop switch (or both for a restart).

I prefer to have universal scripts that find their own BizTalk databases without hardcoding server names into scripts since we have multiple enironments. In this case I get the database server and management database information from WMI.

The next step is to load the BizTalk ExplorerOM and connect it to the BizTalk database.

Finally the applications are filtered on their status and then stopped or started using the ForEach-Object. Once all applications have been set to stop or start call SaveChanges to actually commit the start or stop.

The following example demonstrates using the BizTalkFactory PowerShell Provider.
# declare -start -stop switch parameters
param([switch] $start, [switch] $stop)

 # Get local BizTalk DBName and DB Server from WMI
 $btsSettings = get-wmiobject MSBTS_GroupSetting -namespace 'root\MicrosoftBizTalkServer'
 $dbInstance = $btsSettings.MgmtDbServerName
 $dbName = $btsSettings.MgmtDbName
 
 new-psdrive -name Biztalk -psprovider Biztalk -root biztalk:\ -instance $dbInstance -database $dbName

 if ($stop) { get-childitem -path biztalk:\applications\* | where-object {$_.status -eq "started"} | stop-application }
 
 if ($start) { get-childitem -path biztalk:\applications\* | where-object {$_.status -eq "stopped"} | start-application }
This script sets up the BizTalkFactory PowerShell Provider instead of the BizTalkExplorerOM.

The other difference is piping the filtered applications into the BizTalkFactory PowerShell Provider stop and start methods rather than the foreach-object loop.
Both of these scripts are pretty compact so the deciding factor becomes whether the BizTalkFactory PowerShell Provider is even an option in a specific environment.


I have a third version that follows a pattern Tomas Restrepo uses for starting and stopping host instances.

I mainly like this pattern because it allows for checking the status of an application just before starting or stopping it. In the previous examples the where-object returns a collection of applications which may be dependent on each other so some of them may be started already earlier in the loop.

In this example, I also added the functionality for specifying a specific application. Note the change to the parameter definition and the new get-applications function. If no application is specified then all applications will be processed.

# declare -stop -start switch parameters
param([switch] $start, [switch] $stop, [string] $app)
 
 function start-application($application)
 {
    # If the application is stopped, start it.
    if ( $application.status -eq "Stopped")
    {
      "Starting application " + $application.name
      $application.Start("StartAll")
    }
 }

 function stop-application($application)
 {
    # If the application is started, stop it.
    if ( $application.status -eq "Started")
    {
      "Stopping application " + $application.name
      $application.Stop("StopAll")
    }
 }

 function get-applications()
 {
    #if there is an application specified in the command line parameter, return just that application
    if ($app)
      { return $BizTalkOM.Applications[$app] }
    else
      { return $BizTalkOM.Applications }
 }

 # Get local BizTalk DBName and DB Server from WMI
 $btsSettings = get-wmiobject MSBTS_GroupSetting -namespace 'root\MicrosoftBizTalkServer'
 $dbInstance = $btsSettings.MgmtDbServerName
 $dbName = $btsSettings.MgmtDbName
 
 # Load BizTalk ExplorerOM
 [void] [System.reflection.Assembly]::LoadWithPartialName("Microsoft.BizTalk.ExplorerOM")
 $BizTalkOM = New-Object Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer
 $BizTalkOM.ConnectionString = "SERVER=$dbInstance;DATABASE=$dbName;Integrated Security=SSPI"

 # if no commandline parameters are supplied do a stop and a start
 if ( !($stop) -and !($start) )
 {
    $stop = $true
    $start = $true
 } 

 if ($stop) 
{
   get-applications | %{stop-application($_)}
   
   # Commit changes
   $BizTalkOM.SaveChanges()  
  }

 
 if ($start)
 {
   get-applications | %{start-application($_)}

   # Commit changes
   $BizTalkOM.SaveChanges()  
 }
Just like the other scripts I use stop and start switch parameters and WMI to find out what BizTalk database to use. The stop and start functions give the opportunity to do a little more with each application object. I could have crammed them into one liners but they would not have been as easy to read. Since PowerShell is not very wide-spread where I work readability is important. The drawback is it makes the script look more complicated.

Tuesday, September 3, 2013

Exporting BizTalk Applications with BizTalkFactory PowerShell Provider

I've been reading "Powershell in a month of Lunches" and wanted to have something to apply the lessons to. Since I do a lot of BizTalk deployments, a BizTalk oriented script was a natural choice.

A lot of the scripts out there look like they come from folks who are used to scripting from the BizTalk / btstask perspective rather than really using the power of powershell and the BizTalkFactory PowerShell Provider.

First of all I have the following in my powershell profile so the powershell provider automatically loads every time I run powershell:

$InitializeDefaultBTSDrive = $false
Add-PSSnapin BiztalkFactory.Powershell.Extensions
Function Biztalk: { Set-Location Biztalk: }
Function Biztalk:\ { Set-Location Biztalk:\ }
The script itself is designed to export all applications on a server, including any binding file resources but not the default bindings or the webs, into an msi package. The default bindings are exported separately. It is useful to be able to see what the current bindings are as they may have been modified after installation.

My servers have environment variables that specify the BizTalk server and BizTalk database so these should be replaced as appropriate for your environment.

NOTE: The first line gets wrapped by the blog software!

new-psdrive -name Biztalk -psprovider Biztalk -root biztalk:\ -instance $env:BT_SERVER -database $env:BT_DATABASE

$apps = get-childitem -path biztalk:\applications\*

foreach($app in $apps)
{

  if ($app.isSystem -eq $false)
  {

    $appName = $app.Name

    $spec = Get-ApplicationResourceSpec $app

    foreach ($resource in $spec.ResourceSpec.Resources.Resource)
    {
      if ( ($resource.Type -eq "System.BizTalk:WebDirectory")
         -or  ($resource.Type -eq "System.BizTalk:BizTalkBinding"
         -and $resource.luid -eq "Application/$appName"))
      {
        $spec.ResourceSpec.Resources.RemoveChild($resource)
      }
    }

  export-application $app ".\$appName.msi" $spec
  export-bindings $app ".\$appName.xml"

  }
}
The script goes through all applications and retrieves the resource specification for each one. Web resources and default bindings are removed from the specification before the export-application call. Any bindings added as resources will remain in the resource spec.

The if statement selecting the resource.types may need to be on one line but was wrapped here for readability.

This was a very satisfying little project that I hope you can find a good use for!