|
Each contribution is licensed to you under a License Agreement by its owner, not Microsoft. Microsoft does not guarantee the contribution or purport to grant rights to it.
|
Categories |
Create deployment usage reports(Microsoft)
Script Code
Windows PowerShell
<#
.Synopsis
Create Deployment Usage Reports in .csv format
.Description
This script goes through the event log on Connection Broker and creates reports for:
- RDS Farm usage: user, server, no. of logins, total disconencted session duration and total active session duration.
- RemoteApp Farm usage: user, server, no. of logins, total disconencted session duration and total active session duration.
- VM Farm usage: user, server, no. of logins, total disconencted session duration and total active session duration.
- Personal Desktop usage: user, target, no. of logins.
Information for each category above is saved in .csv file: PD200909171335.csv
RAILFarm200909171335.csv
RDSFarm200909171335.csv
VMFarm200909171335.csv
.Parameter BeforeDate
Events to scan before Specified Date.
.Parameter AfterDate
Events to scan after Specified Date.
.Parameter FileName
FileName where the report should be written. If this parameter is omitted results will be written to the Console.
.Example
PS C:\> DeploymenyStatus.ps1 -AfterDate 09/09/09 -FileName C:\Report.txt
Creates a report starting with 09/09/09 to current date and stores in Report.txt file in C Drive.
#>
param(
[ValidateNotNullOrEmpty()]
[String]
$AfterDate = $null,
[ValidateNotNullOrEmpty()]
[String]
$BeforeDate = $null,
[ValidateNotNullOrEmpty()]
[String]
$FileName = $null
)
#Event Log Name
$EventLogName = "Microsoft-Windows-TerminalServices-SessionBroker/Operational"
#session state
$DisconnSession = 1
$ActiveSession = 0
#events actions
$EventActionDisconn = "Session Disconnected"
$EventActionLogoff = "Session Logged Off"
$EventActionReconn = "Session Reconnected"
#RemoteApp will have this as initial program info in CB database
$ApplicationType = "rdpinit.exe"
$ID787 = 787
$ID786 = 786
$ID801 = 801
#define our PDs
$VMpoolInOurDpl = ("ts-wlbs-vm24", "ts-wlbs-vm15", "XPSP3-RemApp2", "Win7-RemApp2", "ts-wlbs-vm10", "ts-wlbs-vm1")
#initialize the hash tables for each type of connections
$PDsHash = @{}
$VMPoolsHash = @{}
$RDSHFarmHash = @{}
$RAILFarmHash = @{}
$VMPoolTargetsHash = @{}
$TotalRDSFarmHash = @{}
$userReportObj = @{}
$RDSResulthash = @{}
$RAILResulthash = @{}
$VMuserReportObj = @{}
$VMResulthash = @{}
$TotalRDSFarmResulthash = @{}
$PDResultHash = @{}
#FUNCTIONS
function GetTimeInHours([System.TimeSpan]$Time)
{
$days = $Time.Days
$hours = $Time.Hours
$min = $Time.Minutes
$timeInHours = (($days -as [int]) * 24 + ($hours -as [int]) + ($min -as [float]) / 60)
$timeInHours
}
#This function will not calculate the session active or disconn time in case the oldest event is Logoff
#
function GetUsageInfo([HashTable]$AHashTable)
{
$ResultHash = @{}
$LogCount = 0
#Add After and Before time interval logic
if($BeforeDate.length -ne 0)
{
$date = $BeforeDate
}
else
{
$date = get-date
}
foreach($key in $AHashTable.Keys)
{
[System.TimeSpan]$SessionActiveTime = 0
[System.TimeSpan]$SessionDisconnectTime = 0
#reading the events in reverse order, because during processing the oldest event is the last in my list
for ($i = ($AHashTable[$key].Count -1); $i -gt 0; $i--)
{
$Server = $AHashTable[$key][$i].Target
$User = $AHashTable[$key][$i].User
#count the logons for each user
if(($AHashTable[$key][$i].EventID -eq $ID787) -or (($AHashTable[$key][$i].EventID -eq $ID786) -and ($AHashTable[$key][$i].EventAction -match $EventActionReconn)))
{
$LogCount++
}
if(($AHashTable[$key][$i].EventID -eq $ID787) -and (($AHashTable[$key][$i-1].EventID -eq $ID786) -and ($AHashTable[$key][$i-1].EventAction -match $EventActionLogoff)))
{
#user logged on and then logged off
$SessionActiveTime += [DateTime]($AHashTable[$key][$i-1].TimeGenerated) - [DateTime]($AHashTable[$key][$i].TimeGenerated)
}
elseif(($AHashTable[$key][$i].EventID -eq $ID787) -and (($AHashTable[$key][$i-1].EventID -eq $ID786) -and ($AHashTable[$key][$i-1].EventAction -match $EventActionDisconn)))
{
#user logged on and then disconnected
$SessionActiveTime += [DateTime]($AHashTable[$key][$i-1].TimeGenerated) - [DateTime]($AHashTable[$key][$i].TimeGenerated)
}
elseif(($AHashTable[$key][$i].EventID -eq $ID787) -and ( ($AHashTable[$key][$i-1].EventID -eq $ID786) -and ($AHashTable[$key][$i-1].EventAction -match $EventActionReconn)))
{
#user logged on, session is active and logon again = Reconnect
$SessionActiveTime += [DateTime]($AHashTable[$key][$i-1].TimeGenerated) - [DateTime]($AHashTable[$key][$i].TimeGenerated)
}
elseif(($AHashTable[$key][$i].EventID -eq $ID786) -and ($AHashTable[$key][$i-1].EventID -eq $ID786))
{
#User disconnected and then reconnected
if(($AHashTable[$key][$i].EventAction -match $EventActionDisconn) -and ($AHashTable[$key][$i-1].EventAction -match $EventActionReconn))
{
$SessionDisconnectTime += [DateTime]($AHashTable[$key][$i-1].TimeGenerated) - [DateTime]($AHashTable[$key][$i].TimeGenerated)
}
#User reconnected and then disconnected
elseif(($AHashTable[$key][$i].EventAction -match $EventActionReconn) -and ($AHashTable[$key][$i-1].EventAction -match $EventActionDisconn))
{
$SessionActiveTime += [DateTime]($AHashTable[$key][$i-1].TimeGenerated) - [DateTime]($AHashTable[$key][$i].TimeGenerated)
}
#User disconnected and then logoff
elseif(($AHashTable[$key][$i].EventAction -match $EventActionDisconn) -and ($AHashTable[$key][$i-1].EventAction -match $EventActionLogoff ))
{
$SessionDisconnectTime += [DateTime]($AHashTable[$key][$i-1].TimeGenerated) - [DateTime]($AHashTable[$key][$i].TimeGenerated)
}
#User reconnected and then logoff
elseif(($AHashTable[$key][$i].EventAction -match $EventActionReconn) -and ($AHashTable[$key][$i-1].EventAction -match $EventActionLogoff ))
{
$SessionActiveTime += [DateTime]($AHashTable[$key][$i-1].TimeGenerated) - [DateTime]($AHashTable[$key][$i].TimeGenerated)
}
}
}
#get the last event info $i = 0 - this would be the newest event
#last event is Logon
if($AHashTable[$key][0].EventID -eq $ID787)
{
#currently logged on. Logon was the last event
$SessionActiveTime += $date - [DateTime]($AHashTable[$key][0].TimeGenerated)
$LogCount++
}
elseif($AHashTable[$key][0].EventID -eq $ID786)
{
#Disconnect event was last event
if($AHashTable[$key][0].EventAction -match $EventActionDisconn)
{
$SessionDisconnectTime += $date - [DateTime]($AHashTable[$key][0].TimeGenerated)
}
#Reconnect last action
elseif($AHashTable[$key][0].EventAction -match $EventActionReconn)
{
$SessionActiveTime += $date - [DateTime]($AHashTable[$key][0].TimeGenerated)
$LogCount++
}
#Logoff last action - nothing to monitor
}
$SessActiveTimeInHours = GetTimeInHours ([System.TimeSpan]$SessionActiveTime )
$SessDisconnTimeInHours = GetTimeInHours ([System.TimeSpan]$SessionDisconnectTime )
#last event logged is the most recent one - so we save that time
$time = [DateTime]($AHashTable[$key][0].TimeGenerated)
$userReportObj = New-Object PSObject
$userReportObj | Add-Member -MemberType NoteProperty -Name LastLogOnTime -Value $time
$userReportObj | Add-Member -MemberType NoteProperty -Name LogOnCount -Value ($LogCount)
$userReportObj | Add-Member -MemberType NoteProperty -Name SessionActive -Value $SessActiveTimeInHours
$userReportObj | Add-Member -MemberType NoteProperty -Name DisconnectTime -Value $SessDisconnTimeInHours
$userReportObj | Add-Member -MemberType NoteProperty -Name UserName -Value ($key)
$userReportObj | Add-Member -MemberType NoteProperty -Name Target -Value ($AHashTable[$key][0].Target)
$ResultHash[$key] = $userReportObj
}
$ResultHash
}
#create the hash with username key - will contain all events generated for same user
#For PDs
function CreateUserHashForPDs([Array]$Events)
{
$PdUserHash = @{}
foreach ($event in $Events)
{
$username = $event.Properties[0].Value
$target = $event.Properties[1].Value
$targetFQDN = $event.Properties[4].Value
$eventgentime = [DateTime]$event.TimeCreated
#this is an workaround our internal deployment naming conventio. Correct check here is:
# if($PDsTargetNames -contains $target)
#where PDsTargetNames will containg all Personal Desktops in the deployment
if(($VMpoolInOurDpl -notcontains $target) -and ($Servers -notcontains $targetFQDN))
{
#this is PD connection
$eventUserObj = New-Object PSObject
$eventUserObj | Add-Member -MemberType NoteProperty -Name User -Value $username
$eventUserObj | Add-Member -MemberType NoteProperty -Name Target -Value $target
$eventUserObj | Add-Member -MemberType NoteProperty -Name TimeGenerated -Value $eventgentime
$eventUserObj | Add-Member -MemberType NoteProperty -Name EventID -Value $event.Id
#PD hash
if ($PdUserHash[$eventUserObj.User] )
{
$PdUserHash[$eventUserObj.User] += @($eventUserObj)
}
else
{
$PdUserHash[$eventUserObj.User] = @($eventUserObj)
}
}
}
$PdUserHash
}
function GetPDsUsageInfo([HashTable]$AHash)
{
$PdUserResultHash = @{}
foreach($key in $AHash.Keys)
{
$LogonCount = 0
for($i = 0; $i -lt $AHash[$Key].Count; $i++)
{
$LogonCount = $i + 1
}
#we get the info from first event for time as this one will be the most recent one
$time = [DateTime]($AHash[$key][0].TimeGenerated)
$PDuserReportObj = New-Object PSObject
$PDuserReportObj | Add-Member -MemberType NoteProperty -Name LastLogOnTime -Value $time
$PDuserReportObj | Add-Member -MemberType NoteProperty -Name LogOnCount -Value $LogonCount
$PDuserReportObj | Add-Member -MemberType NoteProperty -Name UserName -Value ($key)
$PDuserReportObj | Add-Member -MemberType NoteProperty -Name Target -Value ($AHash[$key][0].Target)
$PdUserResultHash[$key] = $PDuserReportObj
}
$PdUserResultHash
}
write-output "*****START******"
#main
#get the parameters
if( ($BeforeDate.length -eq 0) -AND ($AfterDate.length -eq 0) )
{
$eventlist = Get-winevent @{logname=$EventLogName} | Where {@($ID787,$ID786) -contains $_.Id}
$PDeventlist = Get-winevent @{logname=$EventLogName} | Where {@($ID801) -contains $_.Id}
}
elseif($BeforeDate.length -eq 0)
{
$eventlist = Get-winevent @{logname=$EventLogName; StartTime=(Get-Date ([DateTime]$AfterDate))} | Where {@($ID787,$ID786) -contains $_.Id}
$PDeventlist = Get-winevent @{logname=$EventLogName; StartTime=(Get-Date ([DateTime]$AfterDate))} | Where {@($ID801) -contains $_.Id}
}
elseif($AfterDate.length -eq 0)
{
$eventlist = Get-winevent @{logname=$EventLogName; EndTime=(Get-Date ([DateTime]$BeforeDate))} | Where {@($ID787,$ID786) -contains $_.Id}
$PDeventlist = Get-winevent @{logname=$EventLogName; EndTime=(Get-Date ([DateTime]$BeforeDate))} | Where {@($ID801) -contains $_.Id}
}
else
{
$eventlist = Get-winevent @{logname=$EventLogName;EndTime=(Get-Date ([DateTime]$BeforeDate));StartTime=(Get-Date ([DateTime]$AfterDate))} | Where {@($ID787,$ID786) -contains $_.Id}
$PDeventlist = Get-winevent @{logname=$EventLogName;EndTime=(Get-Date ([DateTime]$BeforeDate));StartTime=(Get-Date ([DateTime]$AfterDate))} | Where {@($ID801) -contains $_.Id}
}
#Get the Farm(s) names
$FarmNames= @(Invoke-Command -ScriptBlock{Import-Module RemoteDesktopServices
$args[0] | %{gi "RDS:\RDSFarms\*"}| %{$_.Name}})
write-output "FARM(S) NAME: " $FarmNames
foreach ($farm in $FarmNames)
{
$Servers= @(Invoke-Command -ScriptBlock{Import-Module RemoteDesktopServices
$args[0] | %{gi "RDS:\RDSFarms\$_\Servers\*"}| %{$_.Name}
} -ArgumentList (,$farm))
write-output "SERVRS in FARM = " $farm "are: " $Servers
}
$VMHostNames = @(Invoke-Command -ScriptBlock{Import-Module RemoteDesktopServices
$args[0] | %{gi "RDS:\ConnectionBroker\VirtualDesktops\RDVHostServers\*" } | %{$_.Name}})
write-output "VM HOST SERVERS: " $VMHostNames
foreach ($vmHost in $VMHostNames )
{
$NoPDsOnHost = @(Invoke-Command -ScriptBlock{Import-Module RemoteDesktopServices
$args[0] | %{gi "RDS:\ConnectionBroker\VirtualDesktops\RDVHostServers\$_\TotalVirtualMachines" } | %{$_.CurrentValue}
} -ArgumentList (,$vmHost))
write-output "PDs on host " $vmHost " : " $NoPDsOnHost
}
if($FarmNames.Count -eq 1)
{
foreach ($servername in $Servers)
{
$TotalNoSessions = @(Invoke-Command -ScriptBlock{Import-Module RemoteDesktopServices
$args[0] | %{gi "RDS:\RDSFarms\$FarmNames\Servers\$_\NumberofSessions" } | %{$_.CurrentValue}
} -ArgumentList (,$servername))
write-output "Sessions on " $servername " : " $TotalNoSessions
}
}
elseif($FarmNames.Count -gt 1)
{
foreach ($farm in $FarmNames)
{
foreach ($servername in $Servers)
{
$TotalNoSessions = @(Invoke-Command -ScriptBlock{Import-Module RemoteDesktopServices
$args[0] | %{gi "RDS:\RDSFarms\$farm\Servers\$_\NumberofSessions" } | %{$_.CurrentValue}
} -ArgumentList (,$servername))
write-output "Sessions on " $servername " : " $TotalNoSessions
}
}
}
$Pools = @(Invoke-Command -ScriptBlock{Import-Module RemoteDesktopServices
$args[0] | %{gi "RDS:\ConnectionBroker\VirtualDesktops\Pools\*" } | %{$_.Name}})
write-output "ALL POOLS IN DEPLOYMENT: " $Pools
foreach($pool in $Pools)
{
$VMPoolTargetsNames += @(Invoke-Command -ScriptBlock{Import-Module RemoteDesktopServices
$args[0] | %{gi "RDS:\ConnectionBroker\VirtualDesktops\Pools\$_\VirtualMachines\*" } | %{$_.Name}
} -ArgumentList (,$pool))
}
$PDsPool = "PersonalVirtualDesktops"
$PDsTargetNames += @(Invoke-Command -ScriptBlock{Import-Module RemoteDesktopServices
$args[0] | %{gi "RDS:\ConnectionBroker\VirtualDesktops\Pools\$PDsPool\VirtualMachines\*" } | %{$_.Name}})
write-output "PDs TARGET SERVERS: " $PDsTargetNames
#create the hash with username key - will contain all events generated for same user
#For RDS Farms hash
foreach ($event in $eventlist)
{
$SessionId = $event.Properties[0].Value
$username = $event.Properties[1].Value
$target = $event.Properties[2].Value
#Event 786 - logged on Logoff, Disconnect and Reconnect session state change
if($event.Id -eq $ID786)
{
$EventAction = $event.Properties[3].Value #this shows the action: Logoff, Reconnect/Disconnect
$Eventfarmname = $event.Properties[4].Value
}
#event 787 - logged for session logon
elseif($event.Id -eq $ID787)
{
$Eventfarmname = $event.Properties[3].Value
$EventAction = $null
}
$eventgentime = [DateTime]$event.TimeCreated
if($FarmNames -contains $Eventfarmname)
{
foreach ($farm in $FarmNames)
{
if($EventFarmName -match $farm)
{
$wmidata = Get-WMIObject -Query ("Select * from Win32_SessionDirectorySession where UserName = '{1}' AND DomainName = '{0}' AND ServerName = '$target'" -f $event.Properties[1].Value.split('\'))
foreach($obj in $wmidata)
{
$SessionState = $obj.SessionState
$AppType = $obj.ApplicationType
#this is a regular RDS/RAIL connection
$RDSFarmCounter++
$eventUserObj = New-Object PSObject
$eventUserObj | Add-Member -MemberType NoteProperty -Name User -Value $username
$eventUserObj | Add-Member -MemberType NoteProperty -Name Target -Value $target
$eventUserObj | Add-Member -MemberType NoteProperty -Name FarmName -Value $Eventfarmname
$eventUserObj | Add-Member -MemberType NoteProperty -Name TimeGenerated -Value $eventgentime
$eventUserObj | Add-Member -MemberType NoteProperty -Name EventID -Value $event.Id
$eventUserObj | Add-Member -MemberType NoteProperty -Name SessionID -Value $SessionId
$eventUserObj | Add-Member -MemberType NoteProperty -Name EventAction -Value $EventAction
if( $SessionState -eq $DisconnSession)
{
$eventUserObj | Add-Member -MemberType NoteProperty -Name Diconnected -Value $true
$eventUserObj | Add-Member -MemberType NoteProperty -Name Active -Value $false
}
else
{
$eventUserObj | Add-Member -MemberType NoteProperty -Name Active -Value $true
$eventUserObj | Add-Member -MemberType NoteProperty -Name Diconnected -Value $false
}
if($AppType -eq $ApplicationType)
{
$eventUserObj | Add-Member -MemberType NoteProperty -Name RailSession -Value $true
#RAIL hash
if ($RAILFarmHash[$eventUserObj.User] )
{
$RAILFarmHash[$eventUserObj.User] += @($eventUserObj)
}
else
{
$RAILFarmHash[$eventUserObj.User] = @($eventUserObj)
}
}
else
{
#RDSH HAsh
$eventUserObj | Add-Member -MemberType NoteProperty -Name RailSession -Value $false
if ($RDSHFarmHash[$eventUserObj.User] )
{
$RDSHFarmHash[$eventUserObj.User] += @($eventUserObj)
}
else
{
$RDSHFarmHash[$eventUserObj.User] = @($eventUserObj)
}
}
#total RDS + RAIL usage - because we cannot accuratelly identify the RAIL based only on event info
if ($TotalRDSFarmHash[$eventUserObj.User])
{
$TotalRDSFarmHash[$eventUserObj.User] += @($eventUserObj)
}
else
{
$TotalRDSFarmHash[$eventUserObj.User] = @($eventUserObj)
}
}
}
}
}
#VM Pools hash
elseif($Eventfarmname -ne $null)
{
foreach($pool in $Pools)
{
if($Eventfarmname -match $pool)
{
#this is a VM pool connection
$VMPoolCounter++
$wmiobj = Get-WMIObject -Query ("Select * from Win32_SessionDirectorySession where UserName = '{1}' AND DomainName = '{0}'" -f $event.Properties[1].Value.split('\'))
foreach( $obj in $wmiobj)
{
$SessionState = $obj.SessionState
$eventUserObj = New-Object PSObject
$eventUserObj | Add-Member -MemberType NoteProperty -Name User -Value $username
$eventUserObj | Add-Member -MemberType NoteProperty -Name Target -Value $target
$eventUserObj | Add-Member -MemberType NoteProperty -Name VMPool -Value $pool
$eventUserObj | Add-Member -MemberType NoteProperty -Name TimeGenerated -Value $eventgentime
$eventUserObj | Add-Member -MemberType NoteProperty -Name EventID -Value $event.Id
$eventUserObj | Add-Member -MemberType NoteProperty -Name EventAction -Value $EventAction
if($SessionState -eq $DisconnSession )
{
$eventUserObj | Add-Member -MemberType NoteProperty -Name Diconnected -Value $true
$eventUserObj | Add-Member -MemberType NoteProperty -Name Active -Value $false
}
else
{
$eventUserObj | Add-Member -MemberType NoteProperty -Name Active -Value $true
$eventUserObj | Add-Member -MemberType NoteProperty -Name Diconnected -Value $false
}
if ($VMPoolsHash[$eventUserObj.User])
{
$VMPoolsHash[$eventUserObj.User] += @($eventUserObj)
}
else
{
$VMPoolsHash[$eventUserObj.User] = @($eventUserObj)
}
}
}
}
}
}
#create the hash with username key - will contain all events generated for same user
#For PD hash
$PDsHash = CreateUserHashForPDs $PDeventlist
write-output "******USER's LIST RDS " $RDSHFarmHash " *******"
write-output "******USER's LIST RAIL" $RAILFarmHash " *******"
write-output "******USER's LIST VMPOOL" $VMPoolsHash " *******"
write-output "******USER's LIST PDs" $PDsHash " *******"
write-output "******USER's LIST TOTAL FARM" $TotalRDSFarmHash " *******"
#get usage info for RDS sessions
$RDSResulthash = GetUsageInfo $RDSHFarmHash
#get usage info for RAIL sessions
$RAILResulthash = GetUsageInfo $RAILFarmHash
#get VM Pool usage
$VMResulthash = GetUsageInfo $VMPoolsHash
#get total RDS + RAIL usage
$TotalRDSFarmResulthash = GetUsageInfo $TotalRDSFarmHash
#get PD usage
$PDResultHash = GetPDsUsageInfo $PDsHash
write-output "+++++++++++++++++++++RDS INFO+++++++++++++++++++++"
$RDSResulthash | select -Expand Values
write-output "+++++++++++++++++++++RAIL INFO+++++++++++++++++++++"
$RAILResulthash | select -Expand Values
write-output "+++++++++++++++++++++VM POOL INFO++++++++++++++++++"
$VMResulthash | select -Expand Values
write-output "+++++++++++++++++++++PDs INFO++++++++++++++++++"
$PDResultHash | select -Expand Values
write-output "+++++++++++++++++++++TOTAL RDS FARM INFO++++++++++++++++++"
$TotalRDSFarmResulthash | select -Expand Values
$Date = get-date -uformat "%Y%m%d%H%M"
$rdsFarmdata = [string]"RDSFarm" + [string]$Date + [string]".csv"
$vmFarmdata = [string]"VMFarm" + [string]$Date + [string]".csv"
$PDdata = [string]"PD" + [string]$Date + [string]".csv"
$RAILFarmdata = [string]"RAILFarm" + [string]$Date + [string]".csv"
$TotalRDSFarmdata = [string]"TotalRDSFarm" + [string]$Date + [string]".csv"
$RDSResulthash.Values | Export-Csv -path $rdsFarmdata -NoTypeInformation
$RAILResulthash.Values | Export-Csv -path $RAILFarmdata -NoTypeInformation
$PDResultHash.Values | Export-Csv -path $PDdata -NoTypeInformation
$VMResulthash.Values | Export-Csv -path $vmFarmdata -NoTypeInformation
$TotalRDSFarmResulthash.Values | Export-Csv -path $TotalRDSFarmdata -NoTypeInformation
if(!([system.string]::IsNullOrEmpty($FileName)))
{
$result | Out-File -Append $FileName
}
else
{
$result | Write-Host
}
Platforms
For online peer support, join
The Official Scripting Guys Forum!
To provide feedback or report bugs in sample scripts, please start a new discussion on the Discussions tab for this script.
Disclaimer
The sample scripts are not supported under any Microsoft standard support program or service. The sample scripts are provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised of the possibility of such damages.
Be the first to create a discussion.
|