<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="https://www.w3.org/2005/Atom" xmlns:sy="https://purl.org/rss/1.0/modules/syndication/" xmlns:dc="https://purl.org/dc/elements/1.1/" xmlns:content="https://purl.org/rss/1.0/modules/content/" xmlns:wfw="https://wellformedweb.org/CommentAPI/" xmlns:slash="https://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>Steve’s blog</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/skeeler/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/skeeler/feed.xml</link>
    <description>Developer tools &amp;amp; technology</description>
    <lastBuildDate>Tue, 16 Apr 2019 05:51:07 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Audit SQL Server stop, start, restart</title>
      <link>https://docs.microsoft.com/archive/blogs/skeeler/audit-sql-server-stop-start-restart</link>
      <pubDate>Mon, 12 Mar 2018 06:20:05 GMT</pubDate>
      <dc:creator><![CDATA[skeeler]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/skeeler/?p=315</guid>
      <description><![CDATA[This article outlines an approach for determining the domain identity of a user who has initiated a...]]></description>
      <content:encoded><![CDATA[<p>This article outlines an approach for determining the domain identity of a user who has initiated a stop, start, or restart request on SQL Server services. Although SQL Server contains server and database auditing functionality as part of the product, this cannot be used to determine the identity of a user changing the service state of a SQL Server instance since that operation is occurring at the system rather than database level.</p>
<hr>
<p>I recently worked with a customer to help resolve an issue with Team Foundation Server where collection databases were not coming online following a service restart. While working on this issue, the following question was posed: <em>&quot;how can we identity the user(s) responsible for stopping, starting, and restarting SQL Server services?&quot;</em>.</p>
<p>Preliminary investigation into using SQL Server audit functionality yielded no solution. Checking with Microsoft database specialists, they confirmed that the closest auditing SQL Server could provide for service restarts would tie that operation to the privileged 'sa' identity. Checking with one of Microsoft's Premier Field Engineers specializing in Security, Liju Varghese provided a quick overview on how to implement service level auditing.</p>
<p>The following sections provide details on the settings required at the operating system level to audit service management operations, in this case for the SQL Server service. This is done using group policy objects.</p>
<h2>Prerequisites</h2>
<p>Ensure the following prerequisites are met prior to proceeding:</p>
<ul>
<li>The Group Policy Editor must be available on the SQL Server machine. If you don’t already have it, you can install using Add Roles &amp; Features wizard:</li>
</ul>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image002.png" alt="image" title="image"></p>
<ul>
<li>Ensure you have sufficient permissions to perform the actions outlined in this document. This may include administrative rights on the local server hosting the SQL Server instance, and possibly domain administrative rights if you are configuring Group Policy Objects at the domain level.</li>
</ul>
<p>The next section, Configuration Details, provides detailed steps on configuring your environment to track SQL Server service starts, stops, and restarts. If you are more interested in seeing the results of this configuration first, you can skip to the following section titled Testing the Configuration to see how service starts, stops, and restarts are reported.</p>
<h2>Configuration Details</h2>
<p>The first step in the process is to enable auditing SACL on the SQL Server service. This must be done on the server that is hosting the SQL Server service. To begin these steps, click on the Windows Start button, then search for and start the Group Policy Management application:</p>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image004.png" alt="image" title="image"></p>
<p>Navigate to the “Group Policy Objects” folder in your domain, right-click on that folder, and select the “New” menu item to create a new group policy object:</p>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image006.png" alt="image" title="image"></p>
<p>Provide a meaningful name for your new GPO and click “OK”:</p>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image008.png" alt="image" title="image"></p>
<p>Right-click on the domain, or suitable OU container to restrict the policy application to only your SQL Server machines, and select the “Link an Existing GPO…” menu option:</p>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image010.png" alt="image" title="image"></p>
<p>Select the newly created “SQL Server Policy” group policy object and click “OK” to link this object:</p>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image012.png" alt="image" title="image"></p>
<p>Confirm that the new “SQL Server Policy” object shows as a linked object to your domain or OU container:</p>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image014.png" alt="image" title="image"></p>
<p>Right-click on the newly created GPO folder and select the “Edit…” menu item:</p>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image016.png" alt="image" title="image"></p>
<p>The “Group Policy Management Editor” will appear next. Expand and locate the SQL Server service name along the following path: <strong>Computer Configuration → Policies → Windows Settings → Security Settings</strong>:</p>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image018.png" alt="image" title="image"></p>
<p>Right-click on your SQL Server service and select the “Properties” menu item, then complete the following dialog window to define a policy for this service:</p>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image020.png" alt="image" title="image"></p>
<p>Next, click the “Edit Security…” button to display the “Security for SQL Server” dialog window. From here, click on the “Advanced” button to display the “Advanced Security Settings” window. From this window, select the “Auditing” tab and then click the “Add” button to create a new audit setting:</p>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image022.png" alt="image" title="image"></p>
<p>In the next window, add “Authenticated Users” as a principal, set the type to “Success”, and check the following permissions: “Start, stop and pause”, “Write”, “Delete”:</p>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image024.png" alt="image" title="image"></p>
<p>In the previous window, you should now see the Success Audit entry like the following:</p>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image026.png" alt="image" title="image"></p>
<p>Enable “Object Access” auditing policies using the Group Policy Management Editor. Navigate to these settings along the following path: <strong>Computer Configuration → Policies → Windows Settings → Security Settings → Advanced Audit Policy Configuration → Audit Policies → Object Access</strong>, as shown in the following image:</p>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image028.png" alt="image" title="image"></p>
<p>Once the steps outlined above have been completed, you are ready to test the configuration by stopping, starting, or restarting the SQL Server instance.</p>
<h2>Testing the Configuration</h2>
<p>Once the GPO configuration has been implemented, you can check the results of stopping, starting, or restarting the SQL Server instance using the following steps.</p>
<p>First, stop, start, or restart the SQL Server instance. There are multiple ways to do this, including: SQL Server Management Studio, the Services (services.msc) application, or from the command line (sc.exe). No matter which method is used, the identity of the user invoking the start, stop, restart operation will be available.</p>
<p>Next, start the Event Viewer application, and navigate to: <strong>Event Viewer (Local) → Windows Logs → Security</strong> and filter on Event ID 4656. You will see a list like the following:</p>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image030.png" alt="image" title="image"></p>
<p>Select each Event ID 4656 and look in the General tab for “Accesses” text labeled either “Stop the service” or “Start the service” for the MSSQLSERVER service, like the following:</p>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image032.png" alt="image" title="image"></p>
<p>The domain and username are displayed in the “Subject” area as shown in the image above.</p>
<p>These events will appear for any direct stop, start, restart operation on the SQL Server. There is one other scenario we need to consider, where a SQL Server instance is restarted due to a complete server restart operation. In that scenario, the events described above will not be present.</p>
<p>To determine the user identity initiating a server restart, you can look in the <strong>Event Viewer (Local) → Windows Logs → Security</strong> section of the Event Viewer and filter by Event ID 1074. This provides information on the user initiating the server restart as shown in the following image:</p>
<p><img src="https://msdnshared.blob.core.windows.net/media/2018/03/aud-image034.png" alt="image" title="image"></p>
<h2>Conclusion</h2>
<p>Hopefully, this information will prove useful in your development and/or infrastructure management activities.</p>
<p>Until next time...</p>]]></content:encoded>
    </item>
    <item>
      <title>Starting Hyper-V virtual machines in sequence</title>
      <link>https://docs.microsoft.com/archive/blogs/skeeler/starting-hyper-v-virtual-machines-in-sequence</link>
      <pubDate>Tue, 09 Jan 2018 08:02:40 GMT</pubDate>
      <dc:creator><![CDATA[skeeler]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/skeeler/?p=305</guid>
      <description><![CDATA[This post describes a simple approach for scripting the startup of multiple Hyper-V virtual machines...]]></description>
      <content:encoded><![CDATA[<p>This post describes a simple approach for scripting the startup of multiple Hyper-V virtual machines in sequence.</p>
<hr />
<p>I do a lot of work with virtualized environments with Hyper-V virtual machines running in a Windows client operating system. I'll often have multiple VMs per environment, with startup dependencies between them. For example, a Team Foundation Server environment using domain authentication and a separate database tier will require 3 VMs, one for Active Directory, one for the SQL Server database, and one for the TFS application tier.</p>
<p>To serialize the startup of these virtual machines and ensure they start in a specific order, I've created the following PowerShell script:</p>
<pre><code class="language-PowerShell">[CmdletBinding()]
Param(
    [Parameter(Position=0)]
    [array]$list = @(&quot;AD&quot;, &quot;SQL&quot;, &quot;TFS&quot;)
)

$start = Get-Date
Write-Output &quot;$(Get-Date $start -Format g) =&gt; Starting the following VMs: $list&quot;

foreach ($item in $list)
{
    $name = &quot;HV-DEMO-&quot; + $item
    Write-Output &quot;&quot;
    Write-Output &quot;Starting VM: $name&quot;
    Start-VM -Name $name
    Write-Output &quot;Waiting for heartbeat...&quot;
    Wait-VM -Name $name -For Heartbeat
    Write-Output &quot;VM $name started and accepting connections&quot;
}

$finish = Get-Date
Write-Output &quot;$(Get-Date $finish -Format g) =&gt; Finished in $(($finish - $start).Minutes) minutes $(($finish - $start).Seconds) seconds&quot;
</code></pre>
<p>Saving this script to a local file named &quot;start-tfs-vms.ps1&quot; and then running it with no parameters (uses the default parameter values) displays the following output:</p>
<div>
<pre style="color: white;background-color: darkblue">  
 Windows PowerShell
 Copyright (C) Microsoft Corporation. All rights reserved.

 PS C:\&gt; start-tfs-vms.ps1
 1/9/2018 6:11 PM =&gt; Starting the following VMs: AD SQL TFS

 Starting VM: HV-DEMO-AD
 Waiting for heartbeat...
 VM HV-DEMO-AD started and accepting connections

 Starting VM: HV-DEMO-SQL
 Waiting for heartbeat...
 VM HV-DEMO-SQL started and accepting connections

 Starting VM: HV-DEMO-TFS
 Waiting for heartbeat...
 VM HV-DEMO-TFS started and accepting connections

 1/9/2018 6:11 PM =&gt; Finished in 0 minutes 42 seconds  

</pre>
</div>
<p>You can easily create another script named &quot;stop-tfs-vms.ps1&quot; that replaces usage of the <code>Start-VM</code> cmdlet with the <code>Stop-VM</code> cmdlet to reverse this operation. Note that you will probably want to change the order of the VM names so they stop in the reverse order.</p>]]></content:encoded>
    </item>
    <item>
      <title>Migrating TFS to a new data center</title>
      <link>https://docs.microsoft.com/archive/blogs/skeeler/migrating-tfs-to-a-new-data-center</link>
      <pubDate>Sun, 04 Jun 2017 13:30:45 GMT</pubDate>
      <dc:creator><![CDATA[skeeler]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/skeeler/?p=295</guid>
      <description><![CDATA[Applies to: TFS 2013 | TFS 2015 | TFS 2017

This article outlines an approach for performing TFS...]]></description>
      <content:encoded><![CDATA[<span style="font-size: 75%">Applies to: TFS 2013 | TFS 2015 | TFS 2017<span></span></span>

This article outlines an approach for performing TFS migrations to a new data center (in a different domain) where there is limited or no connectivity between the current and target environments. In this context, migrating the TFS deployment includes restoring backups of the TFS (config &amp; collections), SharePoint (content), and SSRS (reports) databases in the new domain environment. If you are faced with this scenario, read on. Even if this does not match your scenario, some parts of the information covered here may still be useful when dealing with large numbers of changes to existing TFS, SharePoint, and SSRS user identities and permissions.

<a></a>
<h2>Background</h2>
There are two documented types of moves you can perform on Team Foundation Server: hardware and environment. In a hardware move, you are moving (or cloning) an existing TFS implementation to a new set of servers in the same domain. In an environment move, you are changing the domain of the TFS deployment. Documentation for both types of moves is available here:
<ul>
 	<li><a href="https://www.visualstudio.com/en-us/docs/setup-admin/tfs/admin/move-clone-hardware">Move or Clone Team Foundation Server from one hardware to another</a></li>
 	<li><a href="https://www.visualstudio.com/en-us/docs/setup-admin/tfs/admin/move-across-domains">Move Team Foundation Server from one environment to another</a></li>
</ul>
Both of these articles have the following important note:
<blockquote><em>"In some situations you might want to change the domain of a TFS deployment as well as its hardware. Changing the domain is an environment-based move, and you should never combine the two move types. First complete the hardware move, and then change the environment."</em></blockquote>
But what if your migration is to a new data center or servers located in a different domain, with restricted or no connectivity to the original domain? This scenario can occur when changing data centers, for example during mergers of separate organizations or transfer of responsibility for services. With limited or no connectivity between environments, it is not possible to join the existing TFS deployment to the new domain. In this case, the hardware move is the closer match for what we need to achieve.

Following the instructions in the <a href="https://www.visualstudio.com/en-us/docs/setup-admin/tfs/admin/move-clone-hardware">hardware move</a> documentation gets us though most of the migration process. After the hardware move has been completed we will still need to remap user identities/permissions for TFS, SharePoint, and SSRS. A common element to each of these three areas is the mapping of your user identities from the old domain into the new domain. We'll look at this requirement in the <a href="#UserMappingFile">User Mapping File</a> section below.

One thing you should pay special attention to during the process is ensuring that any TFS user identities in the new domain that you will be mapping TFS users from the old domain into are not part of the <code>Local Administrators</code> group. This is important because the TFS identity synchronization service will automatically create TFS user identities for members of this group. Once a TFS user identity has been created in the new domain, it is not possible to map a TFS user identity from the old domain into it.

<h2 id="UserMappingFile">User Mapping File</h2>
To support the user identity and permission changes needed in the three identified areas, we will create a common user mapping text file. In this schema, <code>OldDomain</code> represents the name of the original domain, <code>OldAccount</code> represents the user account names in the original domain, <code>NewDomain</code> represents the name of the target domain, and <code>NewAccount</code> represents the user account names in the target domain.

The following illustrates a user mapping file for a sample scenario where <code>TFSDEV</code> is the original domain and <code>TFSPRD</code> is the target domain:
<pre><code>OldDomain,OldAccount,NewDomain,NewAccount
TFSDEV,Developer1,TFSPRD,Developer1
TFSDEV,Developer3,TFSPRD,Developer2
</code></pre>
The first line of this file contains the attribute names, which must match exactly for the PowerShell scripts provided later to operate correctly. In this sample file, we are mapping the <code>Developer1</code> account in the <code>TFSDEV</code> domain to the same account name in the <code>TFSPRD</code> domain. The <code>Developer3</code> user account in the <code>TFSDEV</code> domain is mapped to <code>Developer2</code> in the <code>TFSPRD</code> domain.

We've chosen the CSV format for the user mapping file in this example as it is relatively easy to manage with a wide range of application tools and utilities. However, you could also use other formats, with the appropriate changes to the sample scripts. For example, JSON and XML are two possible alternatives.

Once the user mapping file has been created, we can proceed with the changes detailed in the following three sections:
<ol>
 	<li><a href="#TFS-Identities">Remap TFS user identities</a></li>
 	<li><a href="#SharePoint-Identities">Remap SharePoint user identities</a></li>
 	<li><a href="#SSRS-Permissions">Update SSRS user permissions</a></li>
</ol>
<h2 id="TFS-Identities">TFS Identities</h2>
Remapping TFS identities is accomplished using the <a href="https://www.visualstudio.com/en-us/docs/setup-admin/tfs/command-line/tfsconfig-cmd#identities">TfsConfig Identities /change</a> command. The TfsConfig.exe utility is installed with Team Foundation Server, under the <code>\Tools</code> folder of the installation path. If all (or most) of your account names match between the two domain environments, then you can run the command directly, omitting the <code>/account</code> and <code>/toaccount</code> parameters. Often times however, you will need to map many user accounts that have different names in each domain.
<h3>tfs-change-identities.ps1</h3>
The following PowerShell script illustrates how you can handle the more complex account mapping scenario, using the user mapping file created in the previous section. The <code>$TfsConfig</code> parameter in this sample script points to the location of the <code>TfsConfig.exe</code> application in a default installation of TFS 2015. If its location is different in your environment, you will need to either change the default value in the script, or provide the location as a parameter value when invoking the script.
<pre><code class="language-PowerShell">[CmdletBinding()]
param (
    [Parameter(Mandatory=$true, Position=0)]
    [string] $UserMapFile,
    [Parameter(Position=1)]
    [string] $TfsConfig = "C:\Program Files\Microsoft Team Foundation Server 14.0\Tools\TfsConfig.exe"
)

if (-Not (Test-Path $TfsConfig))
{
    Write-Error "TfsConfig.exe file does not exist in the specified location"
    return
}

if (-Not (Test-Path $UserMapFile))
{
    Write-Error "User map file does not exist in the specified location"
    return
}

$usermap = Import-Csv $UserMapFile

foreach ($user in $usermap)
{
    Write-Host "Changing $($user.OldDomain)\$($user.OldAccount)" -NoNewline
    Write-Host " to $($user.NewDomain)\$($user.NewAccount)"

    &amp; $TfsConfig Identities /change `
        /fromdomain:$($user.OldDomain) `
        /todomain:$($user.NewDomain) `
        /account:$($user.OldAccount) `
        /toaccount:$($user.NewAccount)
}
</code></pre>
Running this script with the user mapping file created in the previous section displays the following output:
<div style="background-color: darkblue;color: white;width: 90%">
<pre style="background-color: darkblue;color: white">
PS C:\&gt; .\tfs-change-identities.ps1 c:\docs\usermap.csv
Changing TFSDEV\Developer1 to TFSPRD\Developer1
Account Name           Exists (see note 1)  Matches (see note 2)
TFSPRD\Developer1      True                 False                  Changed
1 security identifier(s) (SIDs) were changed in Team Foundation Server.
== NOTES ==
(1) The Exists column indicates whether the listed account exists in Windows. For the List mode of the command, this is the account stored in Team Foundation Server. For the Change mode, it is the target of the change.
(2) The Matches column indicates whether the SID stored in Team Foundation Server matches with Windows.
</pre>
</div>
The script executes the <code>TfsConfig Identities /change</code> command for each entry in the user mapping file, and will display the <code>Changed</code> value on success. Changed TFS identities will not match the Windows user identities until the next identity synchronization has occurred - by default, this happens at the top of every hour. It is possible to force an identity synchronization to happen immediately by invoking a web service call. Stay tuned for a future post describing that process.

<h2 id="SharePoint-Identities">SharePoint Identities</h2>
After moving your SharePoint content database for TFS to a new domain, the SharePoint user permissions still reference user identities in the original domain environment.
<h3>sp-change-identities.ps1</h3>
It is relatively easy to remap the SharePoint user identities by using the <code>Move-SPUser</code> PowerShell cmdlet. The following PowerShell script illustrates how you can use the previously created <a href="#UserMappingFile">User Mapping File</a> to remap the SharePoint user identities into the new domain environment.
<pre><code>[CmdletBinding()]
param (
    [Parameter(Mandatory=$true, Position=0)]
    [string] $WebAppUrl,
    [Parameter(Mandatory=$true, Position=1)]
    [string] $UserMapFile
)
$ErrorActionPreference = "Stop"
Add-PSSnapIn "Microsoft.SharePoint.Powershell"

$moveErrors = $null
$usermap = Import-Csv $UserMapFile

$users = Get-SPWebApplication $WebAppUrl | Get-SPSite | Get-SPWeb | Get-SPUser

foreach ($user in $users)
{
    $parts = $user.UserLogin.Split('\')
    if ($parts.Count -ne 2) { continue }
    $domain = $parts[0]
    $account = $parts[1]

    foreach ($entry in $usermap)
    {
        if ($domain -eq $entry.OldDomain -and $account -eq $entry.OldAccount)
        {
            $oldAlias = $domain + "\" + $account
            $newAlias = $entry.NewDomain + "\" + $entry.NewAccount
            Write-Host "Changing SharePoint user from [" -ForegroundColor Yellow -NoNewline
            Write-Host $oldAlias "] to [" $newAlias "]" -ForegroundColor Yellow
            Move-SPUser -Identity $user -NewAlias $newAlias -IgnoreSID -ErrorAction SilentlyContinue
        }
    }
}
</code></pre>
Run the above script, supplying the URL of the SharePoint web application associated with the TFS SharePoint site collections, and location of the user mapping file. Typically you will run this on the SharePoint server in the new environment, which should already have the <code>Microsoft.SharePoint.Powershell</code> module present.

Running this script produces the following sample output:
<div style="background-color: darkblue;color: white;width: 90%">
<pre style="background-color: darkblue;color: white">
PS C:\&gt; .\sp-change-identities.ps1 https://tfs-prd-at/ .\usermap.csv
<span style="background-color: darkblue;color: yellow">
Changing SharePoint user from [TFSDEV\Developer1] to [TFSPRD\Developer1]
Changing SharePoint user from [TFSDEV\Developer3] to [TFSPRD\Developer2]</span>
</pre>
</div>
After remapping your SharePoint user identities, login with a user in the new domain and navigate to a SharePoint site associated with one or more of your TFS team projects to validate that the SharePoint user was remapped successfully.

<h2 id="SSRS-Permissions">SSRS Permissions</h2>
The last area we'll address is SQL Server Reporting Services (SSRS) permissions for your TFS team project reports. Managing SSRS user permissions (roles) can be done through the Report Manager web interface as documented in <a href="https://docs.microsoft.com/en-us/sql/reporting-services/security/grant-user-access-to-a-report-server">Grant user access to a report server</a>, or via the command-line with the <a href="https://docs.microsoft.com/en-us/sql/reporting-services/tools/rs-exe-utility-ssrs">RS.exe utility</a>.

This section describes a PowerShell-based solution to simplify the replication of hundreds (or thousands) of distinct SSRS user permissions between different domain environments. We will once again leverage the <a href="#UserMappingFile">User Mapping File</a> already created, combined with two PowerShell scripts to: 1) retrieve existing user permissions in the original domain environment, and 2) reapply those permissions in the new domain environment.
<h3>ssrs-list-permissions.ps1</h3>
The first step is to generate a textual representation of user permissions in the original domain environment. It takes two parameters: the SSRS report server starting path, and an SSRS instance name if this is not a default SSRS instance. The script should be run on the SSRS server in the original domain environment. It can be run from elsewhere by changing the <code>$reportServerUri</code> variable to point to the SSRS Web Service URL (available in Reporting Services Configuration Manager).
<pre><code class="language-powershell">&lt;#
    PowerShell script to iterate over non-inherited SSRS permissions.
    1. Change the $sourceFolderPath value if you want to start at a lower
       level than root of SSRS.
    2. Change the $InstanceName if this is not the SSRS default instance
#&gt;
[CmdletBinding()]
param (
    [Parameter(Position=0)]
    [string] $sourceFolderPath = "/",
    [Parameter(Position=1)]
    [string] $InstanceName = ""
)

if ($InstanceName -eq "")
{
    $reportServerUri = "https://localhost/ReportServer/ReportService2010.asmx"
}
else
{
    $reportServerUri = "https://localhost/ReportServer_" + $InstanceName + "/ReportService2010.asmx"
}

$scriptDirectory = Split-Path $MyInvocation.MyCommand.Path
$outfile = $scriptDirectory + "\ssrs-list-permissions.csv"

Write-Host "SSRS web service:  " $reportServerUri -ForegroundColor Yellow
Write-Host "Working directory: " $scriptDirectory -ForegroundColor Yellow
Write-Host "Computer name:     " $env:COMPUTERNAME -ForegroundColor Yellow
Write-Host "Logged in as:      " $env:USERNAME -ForegroundColor Yellow

$inheritParent = $true
$output = @()

$proxy = New-WebServiceProxy -UseDefaultCredential -Uri $reportServerUri

$items = $proxy.ListChildren($sourceFolderPath, $true) `
    | Select-Object TypeName, Path, Name

foreach ($item in $items)
{
    Add-Member -InputObject $item -MemberType NoteProperty -Name RoleName -Value ''
    Add-Member -InputObject $item -MemberType NoteProperty -Name FullyQualifiedUserName -Value ''
    Add-Member -InputObject $item -MemberType NoteProperty -Name Domain -Value ''
    Add-Member -InputObject $item -MemberType NoteProperty -Name UserName -Value ''

    $needHeader = $true
    foreach ($policy in $proxy.GetPolicies($item.path, [ref]$inheritParent))
    {
        if ($inheritParent) { continue }

        if ($needHeader)
        {
            Write-Host
            Write-Host "Type: " $item.TypeName -ForegroundColor Yellow
            Write-Host "Path: " $item.Path -ForegroundColor Yellow
            Write-Host "Name: " $item.Name -ForegroundColor Yellow
            Write-Host "--------------------------------------------------"
            $needHeader = $false
        }

        Write-Host $policy.GroupUserName -NoNewline
        Write-Host " (" -NoNewline
        $needComma = $false
        foreach ($role in $policy.Roles)
        {
            if ($needComma) { Write-Host ", " -NoNewline } else { $needComma = $true }
            Write-Host $role.Name -ForegroundColor Green -NoNewline
            $temp = $item.PsObject.Copy();
            $temp.RoleName = $role.Name
            $temp.FullyQualifiedUserName = $policy.GroupUserName;
            $temp.Domain = $policy.GroupUserName.Split('\')[0]
            $temp.UserName = $policy.GroupUserName.Split('\')[1]
            $output += $temp;
            $temp.reset;
        }
        Write-Host ")"
    }
}

$output | Export-csv $outfile -NoTypeInformation;

Write-Host
Write-Host "Completed with output stored in: " $outfile
Write-Host
</code></pre>
This script creates an output file named <code>ssrs-list-permissions.csv</code> in the same directory where the script resides. Remember the location and name of this output file as it will be needed in the next part of the process.

Sample console output from running the <code>ssrs-list-permissions.ps1</code> script looks like this:
<div style="background-color: darkblue;color: white;width: 90%">
<pre style="background-color: darkblue;color: white">
PS C:\&gt; .\ssrs-list-permissions.ps1
<span style="background-color: darkblue;color: yellow">
Type:  Folder
Path:  /TfsReports/DefaultCollection/Agile
Name:  Agile
</span>
TFSDEV\Developer1 (<span style="background-color: darkblue;color: lime">Content Manager</span>, <span style="background-color: darkblue;color: lime">Publisher</span>)
TFSDEV\Developer3 (<span style="background-color: darkblue;color: lime">Browser</span>)
Completed with output stored in: ssrs-list-permissions.csv
</pre>
</div>
The permissions output file has the following format:
<pre><code>TypeName,Path,Name,RoleName,FullyQualifiedUserName,Domain,UserName
Folder,/TfsReports/DefaultCollection/Agile,Agile,Content Manager,TFSDEV\Developer1,TFSDEV,Developer1
Folder,/TfsReports/DefaultCollection/Agile,Agile,Publisher,TFSDEV\Developer1,TFSDEV,Developer1
Folder,/TfsReports/DefaultCollection/Agile,Agile,Browser,TFSDEV\Developer3,TFSDEV,Developer3
</code></pre>
If a user is assigned to multiple roles, each role will be specified on a separate line in the permissions output file generated by the <code>ssrs-list-permissions.ps1</code> script.
<h3>ssrs-change-permissions.ps1</h3>
The second step is to use the <a href="#UserMappingFile">User Mapping File</a> along with the permissions output file generated in the previous step. Together, these two files are used as input to the script below that is run on the SSRS server in the new domain environment.

This script uses the SSRS web service to update permission policies. For each object/role permission identified in the the permissions output file generated in the original domain, it performs a lookup in the user mapping file to determine user identity in the new domain, and grants that identity the object/role permission.
<pre><code class="language-powershell">&lt;#
    The first parameter (ssrsPermissionsFile) is the file path to the output
      from the 'ssrs-list-permissions.ps1' script run in the old domain.
    The second parameter (UserMapFile) is the file path to the user mapping file.
    The third parameter (ssrsInstanceName) is the SSRS instance name. If running
      on the default SSRS instance, press &lt;Enter&gt; if prompted to supply
      an empty string value.
#&gt;
[CmdletBinding()]
param (
    [Parameter(Mandatory=$true, Position=0)]
    [string] $ssrsPermissionsFile,
    [Parameter(Mandatory=$true, Position=1)]
    [string] $UserMapFile,
    [Parameter(Mandatory=$true, Position=2)]
    [AllowEmptyString()]
    [string] $ssrsInstanceName
)

if ($ssrsInstanceName -eq "")
{
    $reportServerUri = "https://localhost/ReportServer/ReportService2010.asmx"
}
else
{
    $reportServerUri = "https://localhost/ReportServer_" + $ssrsInstanceName + "/ReportService2010.asmx"
}

if (-Not (Test-Path $ssrsPermissionsFile))
{
    Write-Error $ssrsPermissionsFile " file does not exist in the specified location"
    return
}

if (-Not (Test-Path $UserMapFile))
{
    Write-Error $UserMapFile " file does not exist in the specified location"
    return
}

Write-Host "Loading SSRS permissions (old domain) from file: " $ssrsPermissionsFile
$permissions = Import-Csv $ssrsPermissionsFile

Write-Host "Loading user mapping file: " $UserMapFile
$usermap = Import-Csv $UserMapFile

$lookup = @{}
$usermap | % { $key = $_.OldDomain + "\" + $_.OldAccount; $lookup[$key] = $_ }

$inheritParent = $true

Write-Host "Connecting to SSRS web service: " $reportServerUri
$proxy = New-WebServiceProxy -UseDefaultCredential -Uri $reportServerUri
$type = $proxy.GetType().Namespace
$policyType = "{0}.Policy" -f $type
$roleType = "{0}.Role" -f $type

foreach ($permission in $permissions)
{
    $key = $permission.FullyQualifiedUserName
    $user = $lookup[$key]

    if ($user -ne $null)
    {
        $identity = $user.NewDomain + "\" + $user.NewAccount

        $message = "Retrieving policies for user '{0}' on '{1}'" -f $identity, $permission.Path
        Write-Host $message -ForegroundColor Yellow

        $policies = $proxy.GetPolicies($permission.Path, [ref]$inheritParent);

        $policy = $policies |
            where { $_.GroupUserName -eq $identity } |
            select -First 1

        if (-not $policy)
        {
            $policy = New-Object ($policyType)
            $policy.GroupUserName = $identity
            $policy.Roles = @()
            $policies += $policy
            $message = "Creating policy for user: '{0}'" -f $identity
            Write-Host $message -ForegroundColor Cyan
        }
 
        $role = $policy.Roles |
            where { $_.Name -eq $permission.RoleName } |
            select -First 1

        if (-not $role)
        {
            $role = New-Object ($roleType)
            $role.Name = $permission.RoleName
            $policy.Roles += $role
            $message = "Adding user '{0}' to role '{1}'" -f $identity, $permission.RoleName
            Write-Host $message -ForegroundColor Cyan
        }

        $message = "Saving policies for user '{0}' on '{1}'" -f $identity, $permission.Path
        Write-Host $message -ForegroundColor Cyan
        $proxy.SetPolicies($permission.Path, $policies);
    }
    else
    {
        $message = "'{0}' not found in user mapping file" -f $key
        Write-Host $message -ForegroundColor Red
    }
}
</code></pre>
Sample console output from running the <code>ssrs-change-permissions.ps1</code> script looks like this:
<div style="background-color: darkblue;color: white;width: 90%">
<pre style="background-color: darkblue;color: white">
PS C:\&gt; C:\Scripts\ssrs-change-permissions.ps1 .\ssrs-list-permissions.csv .\usermap.csv ""
Loading SSRS permissions (old domain) from file: ssrs-list-permissions.csv
Loading user mapping file: usermap.csv
Connecting to SSRS web service: https://localhost/ReportServer/ReportService2010.asmx
<span style="background-color: darkblue;color: yellow">Retrieving policies for user 'TFSPRD\Developer1' on '/TfsReports/DefaultCollection/Agile'</span>
<span style="background-color: darkblue;color: cyan">Creating policy for user: 'TFSPRD\Developer1'
Adding user 'TFSPRD\Developer1' to role 'Content Manager'
Saving policies for user 'TFSPRD\Developer1' on '/TfsReports/DefaultCollection/Agile'
Adding user 'TFSPRD\Developer1' to role 'Publisher'
Saving policies for user 'TFSPRD\Developer1' on '/TfsReports/DefaultCollection/Agile'</span>
<span style="background-color: darkblue;color: yellow">Retrieving policies for user 'TFSPRD\Developer2' on '/TfsReports/DefaultCollection/Agile'</span>
<span style="background-color: darkblue;color: cyan">Creating policy for user: 'TFSPRD\Developer2'
Adding user 'TFSPRD\Developer2' to role 'Browser'
Saving policies for user 'TFSPRD\Developer2' on '/TfsReports/DefaultCollection/Agile'</span>
</pre>
</div>
<a></a>
<h2>Conclusion</h2>
We've demonstrated how you can leverage PowerShell scripting to simplify the process of managing user identities in a specific migration scenario for an enterprise-scale TFS deployment.]]></content:encoded>
    </item>
    <item>
      <title>Visual Studio 2017 Launch Event</title>
      <link>https://docs.microsoft.com/archive/blogs/skeeler/visual-studio-2017-launch-event</link>
      <pubDate>Mon, 13 Feb 2017 10:58:16 GMT</pubDate>
      <dc:creator><![CDATA[skeeler]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/skeeler/?p=175</guid>
      <description><![CDATA[Happy 20th birthday Visual Studio! Details on the March 7th &amp; 8th launch event are available...]]></description>
      <content:encoded><![CDATA[<p>Happy 20th birthday Visual Studio!</p> <p>Details on the March 7th &amp; 8th launch event are available here: <a title="https://launch.visualstudio.com/" href="https://launch.visualstudio.com/">https://launch.visualstudio.com/</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Subscription usage and quotas in the Azure portal</title>
      <link>https://docs.microsoft.com/archive/blogs/skeeler/subscription-usage-and-quotas-in-the-azure-portal</link>
      <pubDate>Mon, 30 Jan 2017 13:00:00 GMT</pubDate>
      <dc:creator><![CDATA[skeeler]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/skeeler/?p=135</guid>
      <description><![CDATA[The Azure team recently announced some new features in the preview portal:...]]></description>
      <content:encoded><![CDATA[<p>The Azure team recently announced some new features in the preview portal:</p> <p><a title="https://azure.microsoft.com/en-us/updates/get-early-access-to-new-portal-features-2/" href="https://azure.microsoft.com/en-us/updates/get-early-access-to-new-portal-features-2/">https://azure.microsoft.com/en-us/updates/get-early-access-to-new-portal-features-2/</a></p><!--more--><p>&nbsp;</p> <p>Of particular interest is the new Subscription Usage + quotas view that displays a graphical view of your subscription’s current limits:</p> <p><img title="image" alt="image" src="https://msdnshared.blob.core.windows.net/media/2017/01/image940.png" width="894" height="146"></p> <p>&nbsp;</p> <p>You can filter the list by service, provider, and location, and restrict the view to only those items currently used or all items. The page has a handy link to initiate a quota increase request. Additionally, you can sort by the Usage column in descending order to get an idea of the quotas that are nearing their limit. This is extremely useful for planning future usage increases.</p> <p>The preview portal is located here: <a title="preview.portal.azure.com" href="https://preview.portal.azure.com/">preview.portal.azure.com</a></p> <p>For more detailed information on quotas, please see the following documentation: <a href="https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits">Azure subscription and service limits, quotas, and constraints</a></p>]]></content:encoded>
    </item>
  </channel>
</rss>