Category Archives: Operation

Recover Deleted Manual test Results

  1. Take backup of these 2 tables from backup db:
    • dbo.tbl_TestRun
    • TestResult.tbl_TestResult
  2. You can use export/import wizard of ssms
    • Specify: sourceServer/database and  destinationServer/database

    SSMS

    • Select write query to data transfer

    SelectWriteQueryToDataTransfer

    • Write sql query to fetch data fromm backup db. And make sure sql query is valid.

    ProvideSourceQuery

    • Sample for tbl_testrun table  ( It will copy all test runs of backup db to destination db. Make sure you should not copy RV column)

    select
    PartitionId, TestRunId, Title,CreationDate,LastUpdated,Owner,State,IncompleteTests,DataspaceId,TestPlanId,IterationId,DropLocation,BuildNumber,
    ErrorMessage,StartDate,CompleteDate,PostProcessState,DueDate,Controller,TestMessageLogId,LegacySharePath,TestSettingsId,
    BuildConfigurationId,Revision,LastUpdatedBy,Type,CoverageId,IsAutomated,TestEnvironmentId,Version,PublicTestSettingsId,IsBvt,Comment,
    TotalTests,PassedTests,NotApplicableTests,UnanalyzedTests,IsMigrated,ReleaseUri,ReleaseEnvironmentUri,TestRunContextId , MaxReservedResultId, DeletedOn
    from tbl_TestRun where PartitionId  = 1

    • Sample for Testresult.tbl_testresult ( it will copy all test results of backup db to destination db, Make sure you should not copy RV column)

    select
    PartitionId,
    DataspaceId,
    TestRunId,
    TestCaseRefId,
    TestResultId,
    CreationDate,
    LastUpdated,
    Outcome,
    State,
    Revision,
    DateStarted,
    DateCompleted,
    LastUpdatedBy,
    RunBy,
    ComputerName,
    FailureType,
    ResolutionStateId,
    Owner,
    ResetCount,
    AfnStripId,
    EffectivePointState
    from TestResult.tbl_testresult where PartitionId  = 1

    • Select source and target tables for both tables differently

    SelectTestRunSelectTestResult

    • Make sure export execution is successful

    ExecutionWasSuccessful

  3. Identify all partitionid, dataspaceid and planid for which we want to recover test run/results. Use this query for partitioned = 1 :

    select ds.PartitionId, pr.project_name, pr.project_id, ds.DataspaceCategory, ds.DataspaceId
    from tbl_Dataspace ds
    join tbl_projects pr
    on pr.PartitionId = ds.PartitionId
    and pr.project_id = ds.DataspaceIdentifier
    where ds.PartitionId = 1 and ds.DataspaceCategory = ‘TestManagement’

  4. Execute script updateTestPoints.sql please provide parameter value for @partitionId, @planId, @dataspaceId

    ECLARE @partitionId INT = 0
    DECLARE @planId INT = 0 — The run’s test plan ID
    DECLARE @dataspaceId INT = 0 — The run’s dataspaceId
    DECLARE @status INT
    DECLARE @rowCount INT
    DECLARE @tfError NVARCHAR(255) — used by the macro RAISETFSERROR
    IF (@partitionId <= 0)
    BEGIN
    Select ‘please provide partition id’ as Error
    END
    — No need to update points if run have no associated plan
    IF (@planId <= 0)
    BEGIN
    Select ‘please provide test plan id’ as Error
    END
    IF (@dataspaceId <= 0)
    BEGIN
    Select ‘please provide dataspace id’ as Error
    END
    IF (object_id(‘tempdb..#ResultHistory’) IS NULL)
    BEGIN
    CREATE TABLE #ResultHistory
    (
    PointId INT,
    TestCaseId INT,
    TestRunId INT,
    TestResultId INT
    PRIMARY KEY CLUSTERED (PointId)
    )
    END
    TRUNCATE TABLE #ResultHistory
    ;WITH Points AS
    (
    SELECT point.PointId,
    point.TestCaseId
    FROM dbo.tbl_Point point
    WHERE point.PartitionId = @partitionId
    AND point.PlanId = @planId
    AND point.IsDeleted = 0
    ),
    Results AS
    (
    SELECT point.PointId,
    point.TestCaseId,
    res.TestRunId,
    res.TestResultId,
    ROW_NUMBER() OVER(PARTITION BY point.PointId, point.TestCaseId ORDER BY res.TestRunId DESC, res.TestResultId DESC) AS Rows
    FROM Points point
    LEFT LOOP JOIN TestResult.tbl_TestCaseReference ref
    ON ref.PartitionId = @partitionId
    AND ref.DataspaceId = @dataspaceId
    AND ref.TestCaseId = point.TestCaseId
    AND ref.TestPointId = point.PointId
    LEFT LOOP JOIN TestResult.tbl_TestResult res WITH (FORCESEEK(ix_TestResult_TestCaseRefId2(PartitionId, DataspaceId, TestCaseRefId)))
    ON res.PartitionId = @partitionId
    AND res.DataspaceId = @dataspaceId
    AND res.TestCaseRefId = ref.TestCaseRefId
    INNER LOOP JOIN dbo.tbl_TestRun run
    ON run.PartitionId = @partitionId
    AND run.DataspaceId = @dataspaceId
    AND run.TestRunId = res.TestRunId
    AND run.State <> 255
    WHERE res.Outcome <> 6
    )
    INSERT INTO #ResultHistory(PointId, TestCaseId, TestRunId, TestResultId)
    — For each point in the plan assigned to @testRunId
    — Find the newest non-aborted result and return its date
    SELECT PointId,
    TestCaseId,
    TestRunId,
    TestResultId
    FROM Results
    WHERE Rows = 1
    OPTION (OPTIMIZE FOR (@partitionId UNKNOWN, @dataspaceId UNKNOWN))
    SELECT @status = @@ERROR
    UPDATE dbo.tbl_Point
    SET LastTestRunId = ISNULL(res.TestRunId, 0),
    LastTestResultId = ISNULL(res.TestResultId, 0),
    LastResultState = res.State,
    LastResultOutcome = res.Outcome,
    State = ISNULL(res.EffectivePointState, 1),
    ChangeNumber = ChangeNumber + 1,
    LastUpdated = GETUTCDATE(),
    LastUpdatedBy = ISNULL(res.lastUpdatedBy, point.lastUpdatedBy),
    FailureType = res.FailureType,
    LastResolutionStateId = ISNULL(res.ResolutionStateId, 0),
    OutcomeMigrationDate = GETUTCDATE()
    OUTPUT @partitionId,
    INSERTED.PointId,
    INSERTED.ChangeNumber,
    INSERTED.State,
    INSERTED.Active,
    INSERTED.FailureType,
    INSERTED.PlanId,
    INSERTED.LastTestRunId,
    INSERTED.LastTestResultId,
    INSERTED.TestCaseId,
    INSERTED.AssignedTo,
    INSERTED.LastUpdated,
    INSERTED.LastUpdatedBy,
    0, — IsSuiteChanged
    0, — IsActiveChanged
    INSERTED.LastResolutionStateId,
    INSERTED.SuiteId,
    INSERTED.ConfigurationId,
    GETUTCDATE(),
    INSERTED.LastResultOutcome
    INTO dbo.tbl_PointHistory
    (
    PartitionId,
    PointId,
    ChangeNumber,
    State,
    Active,
    FailureType,
    PlanId,
    LastTestRunId,
    LastTestResultId,
    TestCaseId,
    AssignedTo,
    LastUpdated,
    LastUpdatedBy,
    IsSuiteChanged,
    IsActiveChanged,
    LastResolutionStateId,
    SuiteId,
    ConfigurationId,
    WatermarkDate,
    Outcome
    )
    FROM tbl_Point point
    LEFT LOOP JOIN #ResultHistory newest
    ON point.PointId = newest.PointId
    LEFT LOOP JOIN TestResult.tbl_TestResult res WITH (FORCESEEK(ixc_TestResult_TestRunId_TestResultId(PartitionId, DataspaceId, TestRunId, TestResultId)))
    ON res.PartitionId = @partitionId
    AND res.DataspaceId = @dataspaceId
    AND res.TestRunId = newest.TestRunId
    and res.TestResultId = newest.TestResultId
    WHERE point.PartitionId = @partitionId
    AND point.PlanId = @planId
    AND point.IsDeleted = 0
    OPTION (OPTIMIZE FOR (@partitionId UNKNOWN, @dataspaceId UNKNOWN))
    SELECT @status = @@ERROR

  5. You need to execute step 4 multiple times for each different values.
  6. please take backup of table TestResult.tbl_TestCaseReference Use query similar to this :

    Select
    ref.PartitionId,
    ref.TestCaseRefId,
    ref.DataspaceId,
    ref.TestCaseId,
    ref.TestPointId,
    ref.ConfigurationId,
    ref.AutomatedTestName,
    ref.AutomatedTestStorage,
    ref.AutomatedTestType,
    ref.AutomatedTestId,
    ref.TestCaseTitle,
    ref.TestCaseRevision,
    ref.Priority,
    ref.Owner,
    ref.AreaId,
    ref.CreationDate,
    ref.CreatedBy,
    ref.LastRefTestRunDate,
    ref.AutomatedTestNameHash,
    ref.AutomatedTestStorageHash
    from TestResult.tbl_TestCaseReference  ref
    join TestResult.tbl_TestResult res
    on ref.PartitionId = res.PartitionId
    and ref.DataspaceId = res.DataspaceId
    and ref.TestCaseRefId = res.TestCaseRefId
    join tbl_testrun run
    on res.PartitionId = run.PartitionId
    and res.DataspaceId = run.DataspaceId
    and res.TestRunId= run.TestRunId
    where ref.PartitionId  = 1 — partitionid
    and ref.PartitionId = 0  — dataspaceid
    and run.TestPlanId = 0  — planid

    • First check the output of this query on your sandbox server (ideally nothing should be present there)
    • Then use sql export wizard to copy this date from backup server to sandbox server and use same query
    • While selecting source and destination table, click on “Edit Mappings…” and make sure you enable this option: “Enable identity insert”

    EnableIdentityInsert.png

  7. Execute step 4 and 5 again

 

Remove TestRun Attachments Manually

  1. Find the records

Select * from
tbl_Attachment as a
Join tbl_FileReference as b
on a.PartitionId = b.PartitionId
and a.TfsFileId = b.FileId
and b.OwnerId = 4
where a.FileName like ‘%.wmv’ –<————- Name of file(s) to delete use %% for wildcards multi file
and a.PartitionId > 0
and a.TestRunId > 0
and a.TestResultId > 0
–and a.CreationDate >= ‘2019-07-31 00:10:38.573’
and a.TestRunId = ‘1141269’
and a.TestResultId = ‘100001’

2. Mark them as Deleted

Update b
SET b.DeletedOn = DateAdd(day,-7,GETUTCDATE())

–select *
from
tbl_Attachment as a
Join tbl_FileReference as b
on a.PartitionId = b.PartitionId
and a.TfsFileId = b.FileId
and b.OwnerId = 4
join tbl_TestRun c
on c.PartitionId = a.PartitionId
and c.TestRunId = a.TestRunId
where a.FileName like ‘%.wmv’ –<————- Name of file(s) to delete use %% for wildcards multi file
and a.PartitionId > 0
and a.TestRunId > 0
and a.TestResultId > 0
–and a.CreationDate >= ‘2019-07-31 00:10:38.573’
and a.TestRunId = ‘1141269’
and a.TestResultId = ‘100001’

3. Run the cleanup job manually

DECLARE @jobSource UNIQUEIDENTIFIER = (select HostId from tbl_ServiceHost where HostType=4 and Name like ‘DatabaseName’)
DECLARE @jobList typ_JobQueueUpdateTable
INSERT @jobList VALUES(‘7A3E559E-8EB7-4E90-A4F7-B7A2515D52B9’,15)
EXEC prc_QueueJobs @jobSource, @jobList, 1, 0
Select * from tbl_JobHistory where JobId = ‘7A3E559E-8EB7-4E90-A4F7-B7A2515D52B9’
ORDER BY QueueTime

Select * from tbl_JobQueue where JobId = ‘7A3E559E-8EB7-4E90-A4F7-B7A2515D52B9’
ORDER BY QueueTime

4. Delete the linked records

Delete

–SELECT *
from tbl_Attachment — where FileName like ‘testvideofilefordeletion.mov’
where a.FileName like ‘%.wmv’ –<————- Name of file(s) to delete use %% for wildcards multi file
and a.PartitionId > 0
and a.TestRunId > 0
and a.TestResultId > 0
–and a.CreationDate >= ‘2019-07-31 00:10:38.573’
and a.TestRunId = ‘1141269’
and a.TestResultId = ‘100001’

 

NOTE: Azure DevOps Server 2019.0.1

Change Azure DevOps Server (TFS) Service Account Passwords

Sometimes, you will have to change all service account’s passwords at once either they are expiring or security compliance requiring you to do that. Although we can get on the server and change the service accounts, due to multiple components Azure DevOps Server (formally known as TFS) has, it was quite a trouble to do so.

Components:

  • App Tier
  • Build & Deployment groups agents
  • SSRS

Interfaces:

  • Admin console
  • Windows services
  • Pipeline (Build & Release) definitions

Environments

  • DEV
  • QA
  • Prod

Solution

  • Setup a pipeline to streamline the processes
    • PS to change passwords
    • PS to change SSRS data source
    • App tier
      • tfsconfig Accounts /UpdatePassword /account::$serviceAccountName /Password:$(ServiceAccountPassword-New) /continue
    • PS to change all agents
  • Scripts
  • ChangeServiceAccountPasswords

TF246062: Two or more databases are in conflict because they are each designated as the owners of the following schema: Framework

Symptom

The Azure DevOps Server configuration could not be reconfigured. The following errors were encountered:

TF246062: Two or more databases are in conflict because they are each designated as the owners of the following schema: Framework. The schema is for the host with the following name and ID: <CollectionName>, [GUID]. The databases with this conflict are: Data Source=<SqlServerName>;Initial Catalog=<CollectionDatabaseName-1>;Integrated Security=True;Encrypt=False, Data Source=<SqlServerName>;Initial Catalog=

<CollectionDatabaseName-2>;Integrated Security=True;Encrypt=False. You must specify one and only one database owner for the schema in the searchable SQL Server instances for this deployment of Azure DevOps Server.

Analysis

Snag_478c1c7f.png

  • [Tfs_Configuration].[dbo].[tbl_DatabasePartitionMap] – Get server instance’s ServiceHostId
  • [Tfs_Configuration].[dbo].[tbl_ServiceHost] – Get each collection’s HostId
  • [<CollectionDatabaseName>].[dbo].[tbl_DatabasePartitionMap] – Get the associated database name by scanning all databases with this ServiceHostId. This is how we can restore TFS databases with different names. It replys on the ServiceHostId rather than the database name.

Solution

  • Manually generate new GUIDs and replace them with ServiceHostId in tbl_DatabasePartitionMap and HostId in tbl_ServiceHost
  • Run tfsconfig RemapDBs

How to show as a more specific connecting program for TFS on SQL server

Symptoms

Snag_e7a861.png

We would like to know what sessions belong to TFS. So instead of default “.Net SqlClient Data Provider”, how to change it to something more meaningful? It is good for monitoring system resource usage on the SQL server.

Analysis

You can easily modify the web.config for an ASP.NET application by adding the “Application Name” to achieve this. However, TFS implemented a little bit differently. TFS actually puts ConnectionString into its table (dbo.tbl_Database) inside the database (tfs_Configuration)

Solution

 

Snag_f0800d.png

Open up the dbo.tbl_Database table from Tfs_Configuration and add “Application Name=TFS_App_Tier” (or whatever you prefer) to those records.

SELECT
s.session_id,
s.host_name,
s.program_name,
c.client_net_address,
c.local_net_address,
db_name(s.database_id) as dbname,
s.open_transaction_count,
s.status,
s.login_name,
s.is_user_process,
c.net_transport,
c.encrypt_option,
c.auth_scheme,
c.net_packet_size,
c.client_tcp_port,
c.connection_id
FROM
sys.dm_exec_sessions as s
INNER JOIN sys.dm_exec_connections c on s.session_id = c.session_id

Use AD groups for TFS 2015

AccessLevels

If you are interested to know the broader view of how TFS manage users and groups, you can refer to this post. To recap, here are some pros and cons.

PROs:

  • Take the burden of “approving” user access – Unless it’s for a pretty small company, TFS administrators are not actually approving anybody’s access anyway. They are just doing the actual work. Even worse is that if the access request starts at TFS team, there will be more emails/IMs going back and forward between TFS team and Dev managers before an user’s access is granted. There is yes and no desision and how, where, and what to the access level. The decision makers should be in Dev team’s hand. We just need to make sure all parties fully understand the scope and meaning of those access.
  • Should users left the company, we can just take care the accounts from AD (by Network/Security teams) and not worry too much about maintaining the integrity of user lists especially now TFS has multiple components like Code, Work, Build, and Test to manage. Needless to say those integration outside of TFS like SharePoint and SSRS.

CONs:

  • It is not Agile any more. Now the whole “processes” are more corporate/audit oriented. Team leaders can not easily to utilize boards as planning and communication tools before. I think it is a trade-off, depends on where your organization at in the lifecycle of organizations.
  • More managing overhead. This is kind of contradict to the point above. We tried to eliminate the headache from TFS administrators by adding more layers and groups to the equation. Eventually the AD groups will grow into something that even those Dev teams not sure which group is the right group to put people and what the impact is for different combination of the groups.

Just in case you are still interested in using AD groups alone to manage TFS users. Here are some AD groups you will need.

  • TFS-<Collection>-CollectionAdministartors
  • TFS-<Collection>-CollectionBuildAdministrators
  • TFS-<Collection>-CollectionBuildServiceAccounts
  • TFS-<Collection>-<Project>-ProjectAdministrators
  • TFS-<Collection>-<Project>-ProjectBuildAdministrators
  • TFS-<Collection>-<Project>-<Queue>-QueueAdministrators
  • TFS-<Collection>-<Project>-<Queue>-QueueUsers
  • TFS-<Collection>-<Project>-<Team>-Code-Administrators
  • TFS-<Collection>-<Project>-<Team>-Code-Developers
  • TFS-<Collection>-<Project>-<Team>-Code-Contractors
  • TFS-<Collection>-<Project>-<Team>-Code-Readers
  • TFS-<Collection>-<Project>-<Team>-Board-Administrators
  • TFS-<Collection>-<Project>-<Team>-Board-Members
  • TFS-<Collection>-<Project>-<Team>-Release-PreDeployApprovers
  • TFS-<Collection>-<Project>-<Team>-Release-PostDeployApprovers
  • TFS-<Collection>-<Project>-<Team>-Release-EnvironmentOwners
  • TFS-<Collection>-<Project>-<Team>-Test-Testers

Proposed AD groups for TFS Users

Setup Windows Services using Power Shell and XML

Again, we want to have a way to setup our environments while easily maintaining the mechanism. Separating the configuration settings and scripts is very important since eventually we would like to have our end-users can maintain their data without worrying the code/scripts and vise versa.

First is the template of configuration files.

<Server id="MyServer">
  <Services>
    <Service id="MyService1">
      <DisplayName>My Service 1</DisplayName>
      <BinaryPath>D:MyServiceBin\MyService1.exe</BinaryPath>
      <LogonAsAccount>MyServiceAccount1</LogonAsAccount>
      <LogonAsPassword>MyPassword1</LogonAsPassword>
      <StartType>delayed-auto</StartType>
    </Service>
    <Service id="MyService2">
      <DisplayName>My Service 2</DisplayName>
      <BinaryPath>D:MyServiceBin\MyService2.exe</BinaryPath>
      <LogonAsAccount>MyServiceAccount2</LogonAsAccount>
      <LogonAsPassword>MyPassword2</LogonAsPassword>
      <StartType>delayed-auto</StartType>
    </Service>
  </Services>
</Server>

Second, let’s make a PS script to do the work

function SetupAService ($serviceName, $displayName, $binaryPath, $serviceActName, $serviceActPasswd, $startType) 
{ 
	# verify if the service already exists, and if yes remove it first
	if (Get-Service $serviceName -ErrorAction SilentlyContinue)
	{
		# using WMI to remove Windows service because PowerShell does not have CmdLet for this
		$serviceToRemove = Get-WmiObject -Class Win32_Service -Filter "name='$serviceName'"
		$serviceToRemove.delete()
		"service removed: $serviceName"
	}
	else
	{
		# just do nothing
		"service does not exists"
	}

	"installing service"
	# creating credentials which can be used to run my windows service

	$secpasswd = ConvertTo-SecureString $serviceActPasswd -AsPlainText -Force
	$mycreds = New-Object System.Management.Automation.PSCredential ($serviceActName, $secpasswd)

	# creating widnows service using all provided parameters
	New-Service -name $serviceName -binaryPathName $binaryPath -displayName $displayName -startupType Automatic -credential $mycreds
	if ([string]::Compare($startType,"delayed-auto", $True) -eq 0)
	{
        $scCommand = "sc.exe config '$serviceName' start= delayed-auto"
        write-host "scCommand: $scCommand"
        Invoke-Expression -Command $scCommand -ErrorAction Stop
	}

	"Installation Completed"
}

Then of course, let’s have another PS script to loop through the config file and setup them one by one.

# Source in SetupAService.ps1
. ".\SetupAService.ps1"

### Read the config XML
# [xml]$thisServer = Get-Content $scriptRoot\Config\UAT\MyServer.xml
$configFile = $args[0]
[xml]$thisServer = Get-Content $configFile

### Create a service for each element in the xml file (under services)
$thisServer.Server.Services.Service| foreach {SetupAService $_.id $_.DisplayName $_.BinaryPath $_.LogonAsAccount $_.LogonAsPassword $_.StartType}

OK, from now on, just try to keep the config files up-to-date. If you really committed to this, you can use it to refresh your existing environments as well. next time, I will try to have another script to do the audit.