<?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>Isaiah Williams</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/iwilliams/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/iwilliams/feed.xml</link>
    <description>Thoughts from a developer…</description>
    <lastBuildDate>Tue, 16 Apr 2019 05:12:35 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Security Assessment using Office 365 Secure Score</title>
      <link>https://docs.microsoft.com/archive/blogs/iwilliams/security-assessment-using-office-365-secure-score</link>
      <pubDate>Mon, 26 Mar 2018 10:51:01 GMT</pubDate>
      <dc:creator><![CDATA[IsaiahWilliams]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/iwilliams/?p=2615</guid>
      <description><![CDATA[Numerous partners today are reselling Office 365 through the Cloud Solution Provider (CSP) program....]]></description>
      <content:encoded><![CDATA[<p>Numerous partners today are reselling Office 365 through the Cloud Solution Provider (CSP) program. To ensure customer stickiness it is recommended that each partner include some intellectual property (IP) with their service offering. This additional IP should go beyond great support, it should be something that adds value to the solution such as additional analytics. An example of this could be customized security assessment performed using <a href="https://support.office.com/article/introducing-the-office-365-secure-score-c9e7160f-2c34-4bd0-a548-5ddcc862eaef">Office 365 Secure Score</a>. Secure Score is a numerical summary of a given customer’s security posture within Office 365 based on system configurations, user behavior, and other security-related measurements. It represents the extent to which the customer has adopted security controls available in Office 365, which can help offset the risk of being breached. No online service is completely immune from security breaches; Secure Score should not be interpreted as a guarantee against security breach in any manner. <p>Secure Score information is available through Microsoft Graph. This means a partner can easily obtain this information for each customer. This functionality makes it possible to identify which customers have a security posture that is less than desirable. Using this information, a partner can rapidly perform a security assessment for all their customers that have Office 365 services. Also, it can be utilized to determine which customer could benefit from the following services<p><a href="https://docs.microsoft.com/en-us/azure/multi-factor-authentication/">Microsoft Azure AD Multifactor Authentication</a><p><a href="https://www.microsoft.com/en-us/cloud-platform/enterprise-mobility-security">Microsoft Enterprise Mobility + Security</a><p><a href="https://products.office.com/en-us/exchange/online-email-threat-protection">Microsoft Office 365 Advanced Threat Protection</a><p>If you would like to provide this purposed solution for your customers, then you can take advantage of the <a href="https://github.com/Microsoft/Partner-Smart-Office/">Partner Smart Office</a> opensource project. This project leverages Azure Functions to capture Secure Score information for each customer and stores it in an instance of Azure Cosmos DB. Using this information, you can easily create Power BI reports like the following <p><a href="https://msdnshared.blob.core.windows.net/media/2018/03/image347.png"><img width="644" height="326" title="image" alt="image" src="https://msdnshared.blob.core.windows.net/media/2018/03/image_thumb324.png" border="0"></a></p><p><a href="https://msdnshared.blob.core.windows.net/media/2018/03/image348.png"><img width="644" height="373" title="image" alt="image" src="https://msdnshared.blob.core.windows.net/media/2018/03/image_thumb325.png" border="0"></a></p><p>If there is a new feature you would like for us to add to this project, then please log a request using the <a href="https://github.com/Microsoft/Partner-Smart-Office/issues">issue tracker</a>. Microsoft is committed to our partners and is eager to help build new solutions. Please reach out to your account team if you would like help enhancing your Office 365 service offering.</p>]]></content:encoded>
    </item>
    <item>
      <title>Partner Center Labs</title>
      <link>https://docs.microsoft.com/archive/blogs/iwilliams/partner-center-labs</link>
      <pubDate>Wed, 07 Feb 2018 14:47:02 GMT</pubDate>
      <dc:creator><![CDATA[IsaiahWilliams]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/iwilliams/?p=2595</guid>
      <description><![CDATA[Cloud solution providers achieve scale and profitability by automating management and integrating...]]></description>
      <content:encoded><![CDATA[Cloud solution providers achieve scale and profitability by automating management and integrating their existing customer relationship management, billing, and operational systems with Microsoft’s Partner Center. Using the Partner Center API/SDK, partners can create integrations to manage customer accounts, place orders, manage resellers, manage subscriptions, handle support requests, and monitor customers' Azure consumption. To help partners get started and perform complex operations using the Partner Center API/SDK I have started the <a href="https://github.com/Microsoft/Partner-Center-Labs/">Partner Center Labs</a> opensource project.

This project provides hands on labs that will guide you through performing various operations. Currently there are a limited number of labs available. However, please feel free to submit a request, using the <a href="https://github.com/Microsoft/Partner-Center-Labs/issues">issue tracker</a>, if there is a concept or operation with respect to the Cloud Solution Provider program that you would like to see how it is accomplished. Finally, if there is concept you would like to address or a lab you would like to construct, then please submit a pull request.]]></content:encoded>
    </item>
    <item>
      <title>Data Connector SDK and Partner Center</title>
      <link>https://docs.microsoft.com/archive/blogs/iwilliams/data-connector-sdk-and-partner-center</link>
      <pubDate>Mon, 22 Jan 2018 09:37:11 GMT</pubDate>
      <dc:creator><![CDATA[IsaiahWilliams]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/iwilliams/?p=2576</guid>
      <description><![CDATA[The Data Connector SDK enables you to create custom connector for Power BI and Power Query. These...]]></description>
      <content:encoded><![CDATA[The <a href="https://powerbi.microsoft.com/en-us/blog/data-connectors-developer-preview/">Data Connector SDK</a> enables you to create custom connector for Power BI and Power Query. These connectors have the ability to bind to applications, data sources, and services. All of this enables you to provide rich business intelligence and robust analytics over multiple data sources. Through this SDK you can develop a connector that binds to a Rest API in order to provide a business analysis a friendly view.

There are some great examples of what can be accomplished using this SDK on <a href="https://github.com/Microsoft/DataConnectors">GitHub</a>. In the past I have written about using the <a href="https://blogs.msdn.microsoft.com/iwilliams/2016/08/31/partner-center-api-and-power-bi/">Partner Center API and Power BI</a>, however, that approach has some drawbacks. In order to address those drawbacks I put together a custom data connector that binds to the Partner Center API. The documentation and source for the data connector is available at <a href="https://github.com/isaiahwilliams/Partner-Center-Query">Partner-Center-Query</a>. Please be aware this data connector makes live calls against the Partner Center API. If you have a significant need for reporting then I would recommend you utilize <a href="https://msdn.microsoft.com/en-us/partner-center/power-bi-app-for-direct-partners">Partner Center Analytics app for Power BI (direct partners in CSP)</a> or <a href="https://github.com/Microsoft/Partner-Center-Reporting">Partner-Center-Reporting</a>.
<h2>Walkthrough</h2>
To highlight some of the enhancements and security improvements I wanted to walkthrough a few queries.
<h3>Token Management</h3>
When requesting information through the Partner Center API, you must specify the appropriate credentials. In order to keep these credential secure they should not be stored in clear text. This is accomplished using the OAuth authentication mechanism provided by the Data Connector SDK. The following code shows how OAuth authentication mechanism is setup.
<pre><span>PartnerCenter</span><span> </span><span>=</span><span> </span><span>[</span><span>
    </span><span>Authentication</span><span> </span><span>=</span><span> </span><span>[</span><span>
        </span><span>OAuth</span><span> </span><span>=</span><span> </span><span>[</span><span>
            </span><span>FinishLogin</span><span> </span><span>=</span><span> </span>FinishLogin<span>,</span><span>
            </span><span>Logout</span><span> </span><span>=</span><span> </span>Logout<span>,</span><span>
            </span><span>Refresh</span><span> </span><span>=</span><span> </span>Refresh<span>,</span><span>
            </span><span>StartLogin</span><span> </span><span>=</span><span> </span>StartLogin<span>
        </span><span>]</span><span>
    </span><span>],</span><span>
    </span><span>Label</span><span> </span><span>=</span><span> </span><span>Extension.LoadString</span><span>(</span><span>"DataSourceLabel"</span><span>)</span><span>
</span><span>];</span></pre>
The following code shows how the necessary access token is obtained.
<pre><span>// OAuth Implementation
 
</span><span>FinishLogin</span><span> </span><span>=</span><span> </span><span>(</span><span>context</span><span>,</span><span> </span><span>callbackUri</span><span>,</span><span> </span><span>state</span><span>)</span><span> </span><span>=&gt;</span><span>
    </span><span>let</span><span>
        </span><span>parts</span><span> </span><span>=</span><span> </span><span>Uri.Parts</span><span>(</span>callbackUri<span>)[</span>Query<span>],</span><span>
        </span><span>result</span><span> </span><span>=</span><span> </span><span>if</span><span> </span><span>(</span><span>Record.HasFields</span><span>(</span>parts<span>,</span><span> </span><span>{</span><span>"error"</span><span>,</span><span> </span><span>"error_description"</span><span>}))</span><span> </span><span>then</span><span> 
                    </span><span>error</span><span> </span><span>Error.Record</span><span>(</span>parts<span>[</span><span>error</span><span>],</span><span> </span>parts<span>[</span>error_description<span>],</span><span> </span>parts<span>)</span><span>
                 </span><span>else</span><span>
                    </span>TokenMethod<span>(</span><span>"authorization_code"</span><span>,</span><span> </span><span>"code"</span><span>,</span><span> </span>parts<span>[</span>code<span>])</span><span>
    </span><span>in</span><span>
        </span>result<span>;</span><span>
 
</span><span>Logout</span><span> </span><span>=</span><span> </span><span>(</span><span>token</span><span>)</span><span> </span><span>=&gt;</span><span> </span>logout_uri<span>;</span><span>
 
</span><span>Refresh</span><span> </span><span>=</span><span> </span><span>(</span><span>resourceUrl</span><span>,</span><span> </span><span>refresh_token</span><span>)</span><span> </span><span>=&gt;</span><span> </span>TokenMethod<span>(</span><span>"refresh_token"</span><span>,</span><span> </span><span>"refresh_token"</span><span>,</span><span> </span>refresh_token<span>);</span><span>
 
</span><span>StartLogin</span><span> </span><span>=</span><span> </span><span>(</span><span>resourceUrl</span><span>,</span><span> </span><span>state</span><span>,</span><span> </span><span>display</span><span>)</span><span> </span><span>=&gt;</span><span>
    </span><span>let</span><span>
        </span><span>authorizeUrl</span><span> </span><span>=</span><span> </span>authorize_uri<span> </span><span>&amp;</span><span> </span><span>"?"</span><span> </span><span>&amp;</span><span> </span><span>Uri.BuildQueryString</span><span>([</span><span>
            </span><span>client_id</span><span> </span><span>=</span><span> </span>client_id<span>,</span><span>  
            </span><span>redirect_uri</span><span> </span><span>=</span><span> </span>redirect_uri<span>,</span><span>
            </span><span>resource</span><span> </span><span>=</span><span> </span><span>"https://api.partnercenter.microsoft.com"</span><span>,</span><span>
            </span><span>state</span><span> </span><span>=</span><span> </span>state<span>,</span><span>
            </span><span>response_type</span><span> </span><span>=</span><span> </span><span>"code"</span><span>,</span><span>
            </span><span>response_mode</span><span> </span><span>=</span><span> </span><span>"query"</span><span>,</span><span>
            </span><span>login</span><span> </span><span>=</span><span> </span><span>"login"</span><span>    
        </span><span>])</span><span>
    </span><span>in</span><span>
        </span><span>[</span><span>
            </span><span>LoginUri</span><span> </span><span>=</span><span> </span>authorizeUrl<span>,</span><span>
            </span><span>CallbackUri</span><span> </span><span>=</span><span> </span>redirect_uri<span>,</span><span>
            </span><span>WindowHeight</span><span> </span><span>=</span><span> </span><span>720</span><span>,</span><span>
            </span><span>WindowWidth</span><span> </span><span>=</span><span> </span><span>1024</span><span>,</span><span>
            </span><span>Context</span><span> </span><span>=</span><span> </span><span>null</span><span>
        </span><span>];</span><span>
 
</span><span>TokenMethod</span><span> </span><span>=</span><span> </span><span>(</span><span>grantType</span><span>,</span><span> </span><span>tokenField</span><span>,</span><span> </span><span>code</span><span>)</span><span> </span><span>=&gt;</span><span>
    </span><span>let</span><span>
        </span><span>queryString</span><span> </span><span>=</span><span> </span><span>[</span><span>
            </span><span>client_id</span><span> </span><span>=</span><span> </span>client_id<span>,</span><span>
            </span><span>grant_type</span><span> </span><span>=</span><span> </span>grantType<span>,</span><span>
            </span><span>redirect_uri</span><span> </span><span>=</span><span> </span>redirect_uri<span>
        </span><span>],</span><span>
        </span><span>queryWithCode</span><span> </span><span>=</span><span> </span><span>Record.AddField</span><span>(</span>queryString<span>,</span><span> </span>tokenField<span>,</span><span> </span>code<span>),</span><span>
 
        </span><span>tokenResponse</span><span> </span><span>=</span><span> </span><span>Web.Contents</span><span>(</span>token_uri<span>,</span><span> </span><span>[</span><span>
            </span><span>Content</span><span> </span><span>=</span><span> </span><span>Text.ToBinary</span><span>(</span><span>Uri.BuildQueryString</span><span>(</span>queryWithCode<span>)),</span><span>
            </span><span>Headers</span><span> </span><span>=</span><span> </span><span>[</span><span>
                </span><span>#"Content-type"</span><span> </span><span>=</span><span> </span><span>"application/x-www-form-urlencoded"</span><span>,</span><span>
                </span><span>#"Accept"</span><span> </span><span>=</span><span> </span><span>"application/json"</span><span>
            </span><span>],</span><span>
            </span><span>ManualStatusHandling</span><span> </span><span>=</span><span> </span><span>{</span><span>400</span><span>}</span><span> 
        </span><span>]),</span><span>
        </span><span>body</span><span> </span><span>=</span><span> </span><span>Json.Document</span><span>(</span>tokenResponse<span>),</span><span>
        </span><span>result</span><span> </span><span>=</span><span> </span><span>if</span><span> </span><span>(</span><span>Record.HasFields</span><span>(</span>body<span>,</span><span> </span><span>{</span><span>"error"</span><span>,</span><span> </span><span>"error_description"</span><span>}))</span><span> </span><span>then</span><span> 
                    </span><span>error</span><span> </span><span>Error.Record</span><span>(</span>body<span>[</span><span>error</span><span>],</span><span> </span>body<span>[</span>error_description<span>],</span><span> </span>body<span>)</span><span>
                 </span><span>else</span><span>
                    </span>body<span>
    </span><span>in</span><span>
        </span>result<span>;</span></pre>
Using this approach the user requesting information from the API will be prompted for credentials. Those credentials will be cached using the secure mechanism built into Power BI. No secure information will be stored in clear text. Also, it worth mentioning that this flow utilizes app + user authentication because various API calls only support the app + user flow.
<h3>Paging</h3>
Various methods within the Partner Center API will return results using paging. In other words the number of records is limited for a single request to the API. An example of this would be querying for Azure utilization records. The API will only return a maximum of 1,000 records at once, so you need to make additional requests in order to obtain the complete set of records. This data connector accomplishes this by using the following code when performing requests against the Partner Center API.
<pre><span>PC.ContinuationRequest</span><span> </span><span>=</span><span> </span><span>(</span><span>relativePath</span><span> </span><span>as</span><span> </span>text<span>,</span><span> </span><span>continuationToken</span><span> </span><span>as</span><span> </span>text<span>)</span><span> </span><span>=&gt;</span><span>
    </span><span>let</span><span> 
        </span><span>source</span><span> </span><span>=</span><span> </span><span>Json.Document</span><span>(</span><span>Web.Contents</span><span>(</span><span>"https://api.partnercenter.microsoft.com/v1/"</span><span>,</span><span>
            </span><span>[</span><span>
                </span><span>Headers</span><span> </span><span>=</span><span> </span><span>[</span><span>
                    </span><span>#"Accept"</span><span> </span><span>=</span><span> </span><span>"application/json"</span><span>,</span><span>
                    </span><span>#"MS-ContinuationToken"</span><span> </span><span>=</span><span> </span>continuationToken<span>,</span><span>
                    </span><span>#"MS-PartnerCenter-Application"</span><span> </span><span>=</span><span> </span>application_name<span>
                </span><span>],</span><span> 
                </span><span>RelativePath</span><span> </span><span>=</span><span> </span>relativePath<span>
            </span><span>]</span><span>
        </span><span>))</span><span>
    </span><span>in</span><span> 
        </span>source<span>;</span><span>
 
</span><span>PC.GetPagedResources</span><span> </span><span>=</span><span> </span><span>(</span><span>relativePath</span><span> </span><span>as</span><span> </span>text<span>)</span><span> </span><span>=&gt;</span><span> 
    </span><span>let</span><span>
        </span><span>GetResources</span><span> </span><span>=</span><span> 
            </span><span>(</span><span>relativePath</span><span> </span><span>as</span><span> </span>text<span>)</span><span> </span><span>as</span><span> </span>record<span> </span><span>=&gt;</span><span>
            </span><span>let</span><span>
                </span><span>data</span><span> </span><span>=</span><span> </span>PC.Request<span>(</span>relativePath<span>),</span><span>
                </span><span>items</span><span> </span><span>=</span><span> </span><span>try</span><span> </span>data<span>[</span>items<span>]</span><span> </span><span>otherwise</span><span> </span><span>null</span><span>,</span><span> 
                </span><span>next</span><span> </span><span>=</span><span> </span><span>try</span><span> </span>data<span>[</span>links<span>][</span>next<span>]</span><span> </span><span>otherwise</span><span> </span><span>null</span><span>,</span><span>
                </span><span>res</span><span> </span><span>=</span><span> </span><span>[</span><span>Items</span><span> </span><span>=</span><span> </span>items<span>,</span><span> </span><span>Next</span><span> </span><span>=</span><span> </span>next<span>]</span><span>
            </span><span>in</span><span> 
                </span>res<span>,</span><span>
 
        </span><span>GetResourcesContinuation</span><span> </span><span>=</span><span> 
            </span><span>(</span><span>nextData</span><span>)</span><span> </span><span>as</span><span> </span>record<span> </span><span>=&gt;</span><span>
            </span><span>let</span><span>
                </span><span>relativePath</span><span> </span><span>=</span><span> </span>nextData<span>[</span>uri<span>],</span><span>
                </span><span>data</span><span> </span><span>=</span><span> </span>PC.ContinuationRequest<span>(</span>relativePath<span>,</span><span> </span>nextData<span>[</span>headers<span>]{</span><span>0</span><span>}[</span>value<span>]),</span><span>
                </span><span>items</span><span> </span><span>=</span><span> </span><span>try</span><span> </span>data<span>[</span>items<span>]</span><span> </span><span>otherwise</span><span> </span><span>null</span><span>,</span><span> 
                </span><span>next</span><span> </span><span>=</span><span> </span><span>try</span><span> </span>data<span>[</span>links<span>][</span>next<span>]</span><span> </span><span>otherwise</span><span> </span><span>null</span><span>,</span><span>
                </span><span>res</span><span> </span><span>=</span><span> </span><span>[</span><span>Items</span><span> </span><span>=</span><span> </span>items<span>,</span><span> </span><span>Next</span><span> </span><span>=</span><span> </span>next<span>]</span><span>
            </span><span>in</span><span> 
                </span>res<span>,</span><span>
 
        </span><span>resourcesList</span><span> </span><span>=</span><span> 
            </span><span>List.Generate</span><span>(</span><span>
                </span><span>()</span><span> </span><span>=&gt;</span><span> </span><span>[</span><span>result</span><span> </span><span>=</span><span> </span>GetResources<span>(</span>relativePath<span>)],</span><span> 
                </span><span>each</span><span> </span><span>[</span>result<span>][</span>Items<span>]</span><span> </span><span>&lt;&gt;</span><span> </span><span>null</span><span>,</span><span> 
                </span><span>each</span><span> </span><span>[</span><span>result</span><span> </span><span>=</span><span> </span>GetResourcesContinuation<span>([</span>result<span>][</span>Next<span>])],</span><span>
                </span><span>each</span><span> </span><span>[</span>result<span>][</span>Items<span>])</span><span>
    </span><span>in</span><span>
        </span>resourcesList<span>;</span><span>
 
</span><span>PC.Request</span><span> </span><span>=</span><span> </span><span>(</span><span>relativePath</span><span> </span><span>as</span><span> </span>text<span>)</span><span> </span><span>=&gt;</span><span>
    </span><span>let</span><span> 
        </span><span>source</span><span> </span><span>=</span><span> </span><span>Json.Document</span><span>(</span><span>Web.Contents</span><span>(</span><span>"https://api.partnercenter.microsoft.com/v1/"</span><span>,</span><span>
            </span><span>[</span><span>
                </span><span>Headers</span><span> </span><span>=</span><span> </span><span>[</span><span>
                    </span><span>#"Accept"</span><span> </span><span>=</span><span> </span><span>"application/json"</span><span>,</span><span>
                    </span><span>#"MS-PartnerCenter-Application"</span><span> </span><span>=</span><span> </span>application_name<span>
                </span><span>],</span><span> 
                </span><span>ManualStatusHandling</span><span> </span><span>=</span><span> </span><span>{</span><span>400</span><span>},</span><span>
                </span><span>RelativePath</span><span> </span><span>=</span><span> </span>relativePath<span>
            </span><span>]</span><span>
        </span><span>))</span><span>
    </span><span>in</span><span> 
        </span>source<span>;</span></pre>
The above code will continue to request data from the API until the value for Next is null. This ensures all pages of information are returned. The following provides a small example of how this logic can be used to obtain a complete list of Customers.
<pre><span>PC.Customers</span><span> </span><span>=</span><span> </span><span>()</span><span> </span><span>=&gt;</span><span>
    </span><span>let</span><span>
        </span><span>customersList</span><span> </span><span>=</span><span> </span>PC.GetPagedResources<span>(</span><span>"customers"</span><span>),</span><span> 
        </span><span>customersListTable</span><span> </span><span>=</span><span> </span><span>Table.FromList</span><span>(</span>customersList<span>,</span><span> </span><span>Splitter.SplitByNothing</span><span>(),</span><span> </span><span>null</span><span>,</span><span> </span><span>null</span><span>,</span><span> </span><span>ExtraValues.Error</span><span>),</span><span>
        </span><span>customersRecordTable</span><span> </span><span>=</span><span> </span><span>Table.ExpandListColumn</span><span>(</span>customersListTable<span>,</span><span> </span><span>"Column1"</span><span>),</span><span>
        </span><span>customersTable</span><span> </span><span>=</span><span> </span><span>Table.ExpandRecordColumn</span><span>(</span>customersRecordTable<span>,</span><span> </span><span>"Column1"</span><span>,</span><span> </span><span>{</span><span>"companyProfile"</span><span>,</span><span> </span><span>"relationshipToPartner"</span><span>},</span><span> </span><span>{</span><span>"companyProfile"</span><span>,</span><span> </span><span>"relationshipToPartner"</span><span>}),</span><span>
        </span><span>customersInfoTable</span><span> </span><span>=</span><span> </span><span>Table.ExpandRecordColumn</span><span>(</span>customersTable<span>,</span><span> </span><span>"companyProfile"</span><span>,</span><span> </span><span>{</span><span>"tenantId"</span><span>,</span><span> </span><span>"domain"</span><span>,</span><span> </span><span>"companyName"</span><span>},</span><span> </span><span>{</span><span>"tenantId"</span><span>,</span><span> </span><span>"domain"</span><span>,</span><span> </span><span>"companyName"</span><span>})</span><span>
    </span><span>in</span><span>
        </span>customersInfoTable<span>;</span></pre>
<h3>Outcome</h3>
When everything is put together you can gain valuable insights into your Cloud Solution Provider business. The following figure show an example of a report that you could build.

<a href="https://msdnshared.blob.core.windows.net/media/2018/01/image312.png"><img width="644" height="376" title="image" alt="image" src="https://msdnshared.blob.core.windows.net/media/2018/01/image_thumb306.png" border="0" /></a>

With this particular report you can quickly see over the past 90 days only four Azure subscriptions have been active. Considering there are more than four Azure subscriptions, this should be something that is investigated. There could be a training issue, and when it is corrected it should result increase usage and profits.]]></content:encoded>
    </item>
    <item>
      <title>Cloud Solution Provider: Reseller Relationship</title>
      <link>https://docs.microsoft.com/archive/blogs/iwilliams/cloud-solution-provider-reseller-relationship</link>
      <pubDate>Mon, 04 Dec 2017 13:43:51 GMT</pubDate>
      <dc:creator><![CDATA[IsaiahWilliams]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/iwilliams/?p=2455</guid>
      <description><![CDATA[With respect to the Cloud Solution Provider (CSP) program a reseller relationship is the mechanism...]]></description>
      <content:encoded><![CDATA[With respect to the Cloud Solution Provider (CSP) program a <a href="https://msdn.microsoft.com/en-us/partner-center/request-a-relationship-with-a-customer">reseller relationship</a> is the mechanism that grants a partner the ability to manage a customer’s services or subscriptions. This feature makes it possible for a customer to leverage their existing Azure AD tenant and for them to get services from multiple different agreements (CSP, Direct, EA, MPSA, Open, etc..). Direct partners and indirect resellers can take advantage of this feature for numerous reasons, such as simplifying a customer’s identity management needs, migrating services from another partner, or to provide additional value add services that other partners cannot.

You might have heard the terms 1-tier and 2-tier with respect to CSP. Those terms have been replaced by direct partner and indirect reseller respectively. A direct partner has a relationship with Microsoft and they are responsible for billing and support of services provided to customers. Where an indirect reseller has relation with an <a href="https://partnercenter.microsoft.com/en-us/partner/find-a-provider">indirect provider</a> and they typically can go to market more readily. The following figure provides a graphical representation of the difference between a direct partner and indirect reseller.

<a href="https://msdnshared.blob.core.windows.net/media/2017/12/image96.png"><img width="644" height="278" title="image" style="margin-right: auto;margin-left: auto;float: none" alt="image" src="https://msdnshared.blob.core.windows.net/media/2017/12/image_thumb94.png" border="0" /></a>

The reseller relationship is created when a customer browses to a link, authenticates, and grants the partner delegated administrative privileges (DAP). Microsoft provides two ways to generate the link, through Partner Center and the <a href="https://msdn.microsoft.com/en-us/library/partnercenter/mt712736.aspx">Partner Center API</a>. Only direct partners can utilize the Partner Center API, indirect resellers will need to work with their indirect provider if they need a pragmatic way of performing this task. The process utilized to generate the link can vary for direct partners and indirect resellers because there are circumstances where a partner will not have access to Partner Center.
<h3>Partner Center</h3>
There is a <em>Request a reseller relationship</em> link on the customers page in Partner Center.

<a href="https://msdnshared.blob.core.windows.net/media/2017/12/image100.png"><img width="644" height="139" title="image" style="margin-right: auto;margin-left: auto;float: none" alt="image" src="https://msdnshared.blob.core.windows.net/media/2017/12/image_thumb98.png" border="0" /></a>

When you click on this link it will load a page that contains an email template. This template can be sent directly to a customer, so that they can create the relationship. It is worth mentioning that this email template can be modified to meet any need. The only component that should not be modified is the <a href="https://portal.office.com">https://portal.office.com</a> link because that is what a customer will use to establish the relationship.

<a href="https://msdnshared.blob.core.windows.net/media/2017/12/image101.png"><img width="644" height="418" title="image" style="margin-right: auto;margin-left: auto;float: none" alt="image" src="https://msdnshared.blob.core.windows.net/media/2017/12/image_thumb99.png" border="0" /></a>

Finally, it is important to note that the link is unique for each partner. This means you can extract the link from the template above and develop your own process for distributing it to customers.
<h3>Partner Center API</h3>
Please note that only direct partners can utilize the Partner Center API/SDK. It is possible to utilize the API/SDK to obtain the link. If you are using the Partner Center .NET SDK then you can utilize the following code to request the link

[sourcecode language="csharp"]
// IAggregatePartner partnerOperations;
var customerRelationshipRequest = partnerOperations.Customers.RelationshipRequest.Get();
[/sourcecode]

If you are calling the API directly then you should make a request that is similar to the following

[sourcecode language="plain"]
GET https://api.partnercenter.microsoft.com/v1/customers/relationshiprequests HTTP/1.1 
Authorization: Bearer &lt;token&gt; 
Accept: application/json 
MS-RequestId: ee519026-4c67-4113-bec7-a38aca621bf0 
MS-CorrelationId: 02971f0f-1029-47b2-9fdb-1932f0987470 
X-Locale: en-US 
Host: api.partnercenter.microsoft.com 
Connection: Keep-Alive 
[/sourcecode]

Both requests will return the same link that is available directly from Partner Center.
<h2>Restrictions</h2>
There are a couple of restrictions where a partner cannot establish a reseller relationship. If you try you will receive an error that states “failed to accept delegated administration offer” or something similar.
<h3>Integration Sandbox</h3>
The integration sandbox, sometimes referred to as the TIP (test in production) tenant, is a reseller account that you can utilize to test custom integration with the Partner Center APIs. As of December 2017 you cannot utilize the TIP tenant to establish reseller relationships. This is not allowed for numerous reasons, including the fact changes and transactions made will not appear on your invoice for this reseller. If you attempt to establish a relationship with the TIP tenant, or a customer that was created in this environment, you will receive an error stating it failed to accept the delegated administration offer.
<h3>Regional Authorization</h3>
Each reseller account is only authorized to resell into a single specific region. If you need the ability to resell Microsoft cloud services, through CSP, to customers in Canada and the United States as an example then you will have two separate reseller accounts. In the event you are attempting to establish a relationship with a customer outside of the region where your reseller account is authorized it will fail. The customer can see which country their Azure AD tenant is associated with by browse to <a href="https://portal.office">https://portal.office</a>, authenticating, and opening their <em>Organizational Profile. </em>

<a href="https://msdnshared.blob.core.windows.net/media/2017/12/image102.png"><img width="244" height="164" title="image" alt="image" src="https://msdnshared.blob.core.windows.net/media/2017/12/image_thumb100.png" border="0" /></a>

<a href="https://msdnshared.blob.core.windows.net/media/2017/12/image103.png"><img width="644" height="304" title="image" alt="image" src="https://msdnshared.blob.core.windows.net/media/2017/12/image_thumb101.png" border="0" /></a>]]></content:encoded>
    </item>
    <item>
      <title>Cloud Solution Provider: Pre-consent</title>
      <link>https://docs.microsoft.com/archive/blogs/iwilliams/cloud-solution-provider-pre-consent</link>
      <pubDate>Fri, 01 Dec 2017 15:44:58 GMT</pubDate>
      <dc:creator><![CDATA[IsaiahWilliams]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/iwilliams/?p=2405</guid>
      <description><![CDATA[The Azure Active Directory (AAD) consent framework provides company administrators, and individual...]]></description>
      <content:encoded><![CDATA[The Azure Active Directory (AAD) consent framework provides company administrators, and individual users, a mechanism for governing an application’s access to resources. This framework ensures that only authorized applications can access resources that belong to a specific company or user. While the consent framework provides a vital security mechanism it creates two problems for Cloud Solution Providers, who are developing custom integration. In order to better understand the problems let’s consider the following scenario

<b>Scenario</b>

You have a solution that provides customers with the ability to design custom training paths for their employees. Each customer has the ability to upload customized training materials that include lab guides, sample code, and videos. This solution can be implemented in a dedicate environment, or a customer can utilize a multi-tenant deployment of the solution. Both the dedicated and multi-tenant version utilize <a href="https://azure.microsoft.com/en-us/services/app-service/">Azure App Services</a>, <a href="https://azure.microsoft.com/en-us/services/cosmos-db/">Cosmos DB</a>, <a href="https://azure.microsoft.com/en-us/services/media-services/">Media Services</a>, and <a href="https://products.office.com/en-us/SharePoint/sharepoint-online-collaboration-software">SharePoint Online</a> to provide the necessary portal and a way to version control documents.

In order to ensure each installation of the solution is deployed in a uniform fashion, <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview#template-deployment">Azure Resource Manager (ARM) templates</a> are utilized to create and configure the Azure Resources. <a href="https://developer.microsoft.com/en-us/graph/docs/concepts/overview">Microsoft Graph</a> will be utilized to interact with SharePoint and to obtain various information about the authenticated user such as their display name. When a customer purchases the solution a new customer will be created in Partner Center and an Office 365 subscription will be provisioned. If the customer purchased the dedicated version an Azure subscription will be provisioned, and the resources should be deployed into the region specified by the customer.

<b>Problems</b>

This scenario requires the ability to utilize Microsoft Graph to manage SharePoint documents and read user information. Also, the ARM API is required to create and configure the Azure resources. When attempting to access resources that belong to a specific customer you will be prompted fro consent as shown in the following figure

<a href="https://msdnshared.blob.core.windows.net/media/2017/12/image17.png"><img width="549" height="484" title="image" style="margin-right: auto;margin-left: auto;float: none" alt="image" src="https://msdnshared.blob.core.windows.net/media/2017/12/image_thumb15.png" border="0" /></a>

This is where both of the problems with the consent framework (for Cloud Solution Providers) can happen
<ol>
 	<li>The customer will not have access to the newly created Azure AD tenant until the entire provisioning process has been completed. Since they will not have access, yet, the company administrator is unable to consent to the application utilized to access the ARM API and Microsoft Graph. This will prevent you from being able to deploy the solution into the customer’s Azure subscription, and the portal will not be able to access the Microsoft Graph on the customer's behalf.</li>
 	<li>While it is possible to perform an <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-devhowto-multi-tenant-overview#understanding-user-and-admin-consent">admin consent</a> it is difficult to ensure each customer has performed the operation and that it was done correctly.</li>
</ol>
<b>Solution</b>

Cloud Solution Providers that have a relationship with the customer can leverage <a href="https://github.com/Microsoft/Partner-Center-Explorer/blob/master/docs/Preconsent.md">pre-consent</a> in order to address both of these problems. Pre-consent leverages the delegated administrative privileges that are established when a new customer is created, or when a <a href="https://msdn.microsoft.com/en-us/partner-center/request-a-relationship-with-a-customer">reseller relationship</a> is established, in order to automatically consent to appropriately configured applications. The application in question must be created in the Azure AD tenant associated with the CSP reseller. To configure an application for pre-consent the service principal object associated with the application needs to be added to the <em>AdminAgents </em>group. The following PowerShell cmdlets can be utilized to configure the application

[sourcecode language="powershell"]
Connect-AzureAD
$AppId = 'INSERT-APPLICATION-ID-HERE'
$DisplayName = 'INSERT-APPLICATION-DISPLAY-NAME-HERE'

$g = Get-AzureADGroup | ? {$_.DisplayName -eq 'AdminAgents'}
$s = Get-AzureADServicePrincipal | ? {$_.AppId -eq $AppId}
if ($s -eq $null) { $s = New-AzureADServicePrincipal -AppId $AppId -DisplayName $DisplayName }
Add-AzureADGroupMember -ObjectId $g.ObjectId -RefObjectId $s.ObjectId
[/sourcecode]

<strong>Possible Issue</strong>

Under certain circumstances you might encounter an error stating “the identity of the calling application cannot be established” when requesting an access token. One possible cause for this error is that the service principal object for the application is missing from the customer’s Azure AD tenant. By design applications that are configured for pre-consent will automatically create a service principal object in the customer’s Azure AD tenant. In rare circumstances the service principal object is not create and this error is thrown. You can correct this error by creating a new service principal object that references the application within the customer’s Azure AD tenant. One way this can be accomplished is by invoking the following PowerShell cmdlets

[sourcecode language="powershell"]
Connect-AzureAD –TenantDomain customer.onmicrosoft.com
$AppId = 'INSERT-APPLICATION-ID-HERE' 
$DisplayName = 'INSERT-APPLICATION-DISPLAY-NAME-HERE'

$s = Get-AzureADServicePrincipal | ? {$_.AppId -eq $AppId}
if ($s -eq $null) { $s = New-AzureADServicePrincipal -AppId $AppId -DisplayName $DisplayName } [/sourcecode]

Be sure to modify the AppId, DisplyName and tenant domain values accordingly prior to invoking the cmdlets.]]></content:encoded>
    </item>
    <item>
      <title>Migrating to Exchange Online</title>
      <link>https://docs.microsoft.com/archive/blogs/iwilliams/migrating-to-exchange-online</link>
      <pubDate>Thu, 20 Apr 2017 14:32:52 GMT</pubDate>
      <dc:creator><![CDATA[IsaiahWilliams]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/iwilliams/?p=2375</guid>
      <description><![CDATA[There are numerous third party tools available today that will help with migrating from just about...]]></description>
      <content:encoded><![CDATA[<p>There are numerous third party tools available today that will help with migrating from just about any email platform to Office 365. While these tools are extremely valuable it is possible to leverage the built in tools to perform the migration in certain scenarios. All of these methods of migration require that someone with elevate privileges to perform the operations which can present a challenge to a customer of a partner who does not have the necessary permissions. Also, it possible that the individuals responsible for the migration will not have the appropriate privileges either. The good news is that the built in tools provide a way to automate the migration, which means it is possible to provide a way to perform the migration and not require elevate privileges.</p> <p>In order to help Cloud Solution Provider partners who find themselves in this type of scenario I have put together the <a href="https://github.com/Microsoft/HEX-Migration-Toolkit">HEX Migration Toolkit</a>. It is a web portal protected by Azure AD authentication that enables individuals from a customer to add new environments to be migrated and create migration batches. When a migration batch is created the customer has to specify a date for when the batch migration should start. This enables the customer to schedule the <a href="https://support.office.com/en-us/article/Cutover-migration-to-Office-365-9496e93c-1e59-41a8-9bb3-6e8df0cd81b4?ui=en-US&amp;rs=en-US&amp;ad=US">cutover migration</a>. This toolkit is an open source project that only has community based support. Additional functionality will be added to this tool over time, but I wanted to take this opportunity to introduce it and allow partners to provide feedback through the <a href="https://github.com/Microsoft/HEX-Migration-Toolkit/issues">issue tracker</a>. </p> <p>Whenever a user logins into the portal the necessary validation tasks are performed to verify the authenticating user belongs to a customer that has a relationship with the configured partner, and that the user has the necessary privileges to access this portal (these privileges are configurable by the partner when the solution is implemented). After the user has successfully authenticated they can create new environments or manage existing ones. </p> <p><a href="https://msdnshared.blob.core.windows.net/media/2017/04/hex01.png"><img title="hex01" style="border-left-width: 0px;border-right-width: 0px;border-bottom-width: 0px;padding-top: 0px;padding-left: 0px;margin-left: 0px;padding-right: 0px;border-top-width: 0px;margin-right: 0px" border="0" alt="hex01" src="https://msdnshared.blob.core.windows.net/media/2017/04/hex01_thumb.png" width="644" height="104"></a></p> <p><a href="https://msdnshared.blob.core.windows.net/media/2017/04/hex02.png"><img title="hex02" style="border-left-width: 0px;border-right-width: 0px;border-bottom-width: 0px;padding-top: 0px;padding-left: 0px;padding-right: 0px;border-top-width: 0px" border="0" alt="hex02" src="https://msdnshared.blob.core.windows.net/media/2017/04/hex02_thumb.png" width="644" height="247"></a></p> <p>When an environment is created the toolkit will enqueue a request to create a migration endpoint in Exchange Online and request a list of mailboxes from the configured endpoint. All of this is accomplished by an <a href="https://docs.microsoft.com/en-us/azure/app-service-web/websites-webjobs-resources#overview">Azure WebJob</a> that leverages <a href="https://technet.microsoft.com/en-us/library/dd335083(v=exchg.160).aspx">PowerShell Remoting</a> to perform the specified tasks. It worth noting that the customer will not need to enter Office 365 details at this point because the partner should have already provisioned the appropriate Office 365 subscription for the customer, and the partner has delegated administrative privileges over the tenant which enables this toolkit to perform operations in Exchange Online on the customers behalf. Since credentials for the customer’s environment are required to perform additional operations it is vital that those credentials be protected. In order to provide the proper protection all passwords are stored in an instance of <a href="https://azure.microsoft.com/en-us/services/key-vault/">Azure Key Vault</a>. Key Vault utilizes hardware security modules to protect this sensitive information.</p> <p><a href="https://msdnshared.blob.core.windows.net/media/2017/04/hex03.png"><img title="hex03" style="border-left-width: 0px;border-right-width: 0px;border-bottom-width: 0px;padding-top: 0px;padding-left: 0px;padding-right: 0px;border-top-width: 0px" border="0" alt="hex03" src="https://msdnshared.blob.core.windows.net/media/2017/04/hex03_thumb.png" width="644" height="106"></a></p> <p>Since there is a chance new mailboxes might be added after the creation of the environment the ability to refresh an environment is important. When a customer clicks on the <em>Refresh</em> link the toolkit will ensure the migration endpoint has been created and request a complete list of the current mailboxes from the configured endpoint. When a customer clicks on the <em>Delete</em> link it will delete all migration batches and the environment information from the toolkit. When a customer clicks on the <em>Batches</em> link they will be taken to page that allows them to create, delete, and view existing migration batches.</p> <p>When a new migration batch is added the customer must select which mailboxes they would like to be a member of the batch. Currently the ability to modify members of a batch after it has been created is not supported. </p> <p><a href="https://msdnshared.blob.core.windows.net/media/2017/04/hex05.png"><img title="hex05" style="border-left-width: 0px;border-right-width: 0px;border-bottom-width: 0px;padding-top: 0px;padding-left: 0px;padding-right: 0px;border-top-width: 0px" border="0" alt="hex05" src="https://msdnshared.blob.core.windows.net/media/2017/04/hex05_thumb.png" width="644" height="241"></a></p> <p>It is recommended that the value for the target delivery domain be set to the [tenant-name].mail.onmicrosoft.com. Be sure to replace the tenant-name value with the appropriate value based on the customer Azure AD tenant. As an example if the customer’s Azure AD tenant is fabrikam.onmicrosoft.com then the target delivery domain value should be configured to fabrikam.mail.onmicrosoft.com. </p> <p><a href="https://msdnshared.blob.core.windows.net/media/2017/04/hex06.png"><img title="hex06" style="border-left-width: 0px;border-right-width: 0px;border-bottom-width: 0px;padding-top: 0px;padding-left: 0px;padding-right: 0px;border-top-width: 0px" border="0" alt="hex06" src="https://msdnshared.blob.core.windows.net/media/2017/04/hex06_thumb.png" width="644" height="106"></a></p> <p>If you would like information on how to deploy this project then checkout the <a href="https://github.com/Microsoft/HEX-Migration-Toolkit/blob/master/docs/Deployment.md">deployment documentation</a>. Additional functionality will be added to the toolkit overtime. There are certain elements that I have already identified, such as reporting, that needs to be added in order to make this a more complete solution. If you would like to utilize this toolkit prior toolkit before I can get reporting added you can leverage Power BI to construct the reports that you need. Since this toolkit utilizes Azure storage tables to store all migration details (except credentials) you can easily construct any report that you see fit. </p>]]></content:encoded>
    </item>
    <item>
      <title>CSP Development Recommendations</title>
      <link>https://docs.microsoft.com/archive/blogs/iwilliams/csp-development-recommendations</link>
      <pubDate>Wed, 19 Apr 2017 16:23:25 GMT</pubDate>
      <dc:creator><![CDATA[IsaiahWilliams]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/iwilliams/?p=2217</guid>
      <description><![CDATA[Designing a system to manage resources typically done through Partner Center can be a complex task...]]></description>
      <content:encoded><![CDATA[Designing a system to manage resources typically done through Partner Center can be a complex task based on the requirements for the application being developed. If we were to consider a portal that provides the ability to manage customers, subscriptions, and users it is likely that the portal will leverage Microsoft Graph, Partner Center API, and various other APIs in order to provide a complete solution. All of these components present a challenge for the developers in that they need monitor the health of each call made, and ensure that the credentials and keys utilized to access these APIs are protected. Fortunately there is two Azure services that any developer can leverage in order to obtain the insight needed to ensure their application is healthy and ensure that the credentials and keys are stored in a secure manner.

<a href="https://azure.microsoft.com/en-us/services/application-insights/">Application Insights</a>

Application Insights can be utilized to monitor the health of a live application. It will automatically detect performance anomalies, and provide powerful analytics to help you diagnose issues and understand what end-users are actually doing with your website. While you can add this service to any web application without changing any code I would recommend that you add custom events to obtain additional insights. If we were to consider the call the Partner Center API to obtain a list of subscriptions it might be helpful to know how long it took the request to complete and who actually made the request. The following sample code will show how you can the custom events and metrics

&nbsp;
<div class="wlWriterEditableSmartContent" id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:45f9855e-5885-461a-b0bd-f7ee42816c5b" style="float: none;margin: 0px;padding: 0px">

[sourcecode language="csharp" padlinenumbers="true"]
/// &lt;summary&gt;
/// Gets the subscriptions for the specified customer.
/// &lt;/summary&gt;
/// &lt;param name=&quot;customerId&quot;&gt;Identifier for the customer.&lt;/param&gt;
/// &lt;returns&gt;A list of subscriptions for the customer.&lt;/returns&gt;
/// &lt;exception cref=&quot;ArgumentException&quot;&gt;
/// &lt;paramref name=&quot;customerId&quot;/&gt; is empty or null.
/// &lt;/exception&gt;
public async Task&lt;List&lt;Subscription&gt;&gt; GetSubscriptionsAsync(string customerId)
{
    CustomerPrincipal principal;
    DateTime startTime;
    Dictionary&lt;string, double&gt; eventMetrics;
    Dictionary&lt;string, string&gt; eventProperties;
    Guid correlationId;
    IPartner operations;
    ResourceCollection&lt;Subscription&gt; subscriptions;

    customerId.AssertNotEmpty(nameof(customerId));

    try
    {
        startTime = DateTime.Now;
        correlationId = Guid.NewGuid();
        operations = await GetAppOperationsAsync(correlationId);

        principal = (CustomerPrincipal)HttpContext.Current.User;

        if (principal.CustomerId.Equals(service.Configuration.PartnerCenterApplicationTenantId))
        {
            subscriptions = await operations.Customers.ById(customerId).Subscriptions.GetAsync();
        }
        else
        {
            subscriptions = await operations.Customers.ById(principal.CustomerId).Subscriptions.GetAsync();
        }

        // Track the event measurements for analysis.
        eventMetrics = new Dictionary&lt;string, double&gt;
        {
            { &quot;ElapsedMilliseconds&quot;, DateTime.Now.Subtract(startTime).TotalMilliseconds },
            { &quot;NumberOfSubscriptions&quot;, subscriptions.TotalCount }
        };

        // Capture the request for the customer summary for analysis.
        eventProperties = new Dictionary&lt;string, string&gt;
        {
            { &quot;CustomerId&quot;, principal.CustomerId },
            { &quot;Name&quot;, principal.Name },
            { &quot;ParternCenterCorrelationId&quot;, correlationId.ToString() }
        };

        service.Telemetry.TrackEvent(&quot;GetSubscriptionsAsync&quot;, eventProperties, eventMetrics);

        return new List&lt;Subscription&gt;(subscriptions.Items);
    }
    finally
    {
        eventMetrics = null;
        eventProperties = null;
        operations = null;
        principal = null;
    }
}[/sourcecode]

</div>
The snippet of code above is part of the <a href="https://github.com/Microsoft/Partner-Center-Explorer/blob/master/src/Explorer/Logic/PartnerOperations.cs">Partner Center Explorer</a> open source sample project. Through this code the elapsed time, number of subscriptions returned, customer identifier and name of the calling user, and the correlation identifier used when calling the Partner Center API are all capture as a custom event. It is worth mentioning that you can configure a handful of additional headers when interacting with the Partner Center API, as documented <a href="https://msdn.microsoft.com/en-us/library/partnercenter/dn974951.aspx">here</a>. It is recommended that you specify a unique identifier for calls to the API using the <em>x-ms-correlation-id</em> header so it easier to to tie error responses back to original requests when debugging. Also, you should specify an application name using the <em>MS-PartnerCenter-Application </em>header, so that support can easily identify all requests coming from your application. If you are using the .NET SDK all of this accomplished when you initialize a new instance of the partner service. The follow snippet of code shows how this is accomplished
<div class="wlWriterEditableSmartContent" id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:fe110082-3529-4fa1-814a-a57a897df152" style="float: none;margin: 0px;padding: 0px">

[sourcecode language="csharp"]
Guid correlationId = Guid.NewGuid();

IPartnerCredentials credentials = await PartnerCredentials.Instance.GenerateByApplicationCredentialsAsync(
    &quot;PartnerCenterApplicationId&quot;,
    &quot;PartnerCenterApplicationSecret&quot;,
    &quot;PartnerCenterApplicationTenantId&quot;);

IAggregatePartner appOperations = PartnerService.Instance.CreatePartnerOperations(credentials);
IPartner operations = appOperations.With(RequestContextFactory.Instance.Create(correlationId));

PartnerService.Instance.ApplicationName = &quot;Application Name&quot;;

Customer customer = await operations.Customers.ById(&quot;customerId&quot;).GetAsync(); [/sourcecode]

</div>
When you run a query against Application Insights for a custom event you will be able to get all the details pertaining to the event. This information can be used to configure alerts or create visualization through Power BI. The figure below show some of the data that will be returned when the <em>GetSubscriptionsAsync </em>function from above is invoked

<a href="https://msdnshared.blob.core.windows.net/media/2017/04/query012.png"><img width="644" height="289" title="query01" style="float: none;padding-top: 0px;padding-left: 0px;margin-left: auto;padding-right: 0px;margin-right: auto;border-width: 0px" alt="query01" src="https://msdnshared.blob.core.windows.net/media/2017/04/query01_thumb2.png" border="0" /></a>

When you capture telemetry data similar to this it makes it possible for you to determine if the dependent APIs calls are resulting in performance issues for your application. Also, it allows you to get insights into how end-users are using your application. If you would like to learn more about Application Insight then checkout the <a href="https://docs.microsoft.com/en-us/azure/application-insights/app-insights-overview">What is Application Insight?</a> documentation. To learn more about the integration with Power BI then check out <a href="https://powerbi.microsoft.com/en-us/documentation/powerbi-content-pack-application-insights/">Application Insights content pack for Power BI</a>.

&nbsp;

<a href="https://azure.microsoft.com/en-us/services/key-vault/">Key Vault</a>

Most developers store connection strings, secret keys, and service account credentials in configuration files such as the web.config file. While this approach provides an easy way to consume this sensitive information it does not protect the information in the event the site, or server, were to be compromised. If an authorized individual managed to get a hold of the client identifier and secret key utilized to access the Partner Center API then there is real potential for damage to your customers or fraud. Azure Key Vault is a service that can encrypt keys and secrets (such as authentication keys, storage account keys, data encryption keys, .PFX files, and passwords) by using keys that are protected by hardware security modules (HSMs).

It is recommended that you leverage an instance of Key Vault to store connection strings, secret keys, and service account credentials to ensure they are protected. In order to access secrets stored in an instance of Key Vault you will create the appropriate security principal and grant it the appropriate privileges. This ensure that the authenticated entity can only perform the operations that you select and it provides a way to audit who, or what, has interacted with the privileged information. Since connection strings and secret keys may need to be utilized prior to prompting a user for credentials I recommend that you grant an Azure AD application the ability to get information from Key Vault. You can authenticate an Azure AD application using a client identifier and secret key or with a client identifier and a certificate. My recommendation would be to utilize a certificate, so that you do not have to store any secret keys in any configuration files. You can read more about this at <a href="https://docs.microsoft.com/en-us/azure/key-vault/key-vault-use-from-web-application#authenticate-with-a-certificate-instead-of-a-client-secret">Authenticating with a Certificate instead of Client Secret</a>, and if you want step by step direction on to configure this please check out <a href="https://github.com/Microsoft/Partner-Center-Explorer/blob/master/docs/KeyVault.md" title="https://github.com/Microsoft/Partner-Center-Explorer/blob/master/docs/KeyVault.md">https://github.com/Microsoft/Partner-Center-Explorer/blob/master/docs/KeyVault.md</a>.

You can find an example of how this approach to securing privileged information at <a href="https://github.com/Microsoft/Partner-Center-Explorer/blob/master/src/Explorer/Configuration/Configuration.cs">Configuration.cs</a> and <a href="https://github.com/Microsoft/Partner-Center-Explorer/blob/master/src/Explorer/Security/VaultService.cs">VaultService.cs</a>.]]></content:encoded>
    </item>
    <item>
      <title>Partner Center and the Bot Framework</title>
      <link>https://docs.microsoft.com/archive/blogs/iwilliams/partner-center-and-the-bot-framework</link>
      <pubDate>Thu, 16 Mar 2017 18:08:32 GMT</pubDate>
      <dc:creator><![CDATA[IsaiahWilliams]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/iwilliams/?p=2215</guid>
      <description><![CDATA[The Microsoft Bot Framework is a comprehensive offering that is utilized to build and deploy high...]]></description>
      <content:encoded><![CDATA[<p>The Microsoft Bot Framework is a comprehensive offering that is utilized to build and deploy high quality bots for users to enjoy. It provides just what is needed to build, connect, manage and publish intelligent bots that interact naturally wherever users are talking – from text/SMS to Skype, Slack, Facebook Messenger, Kik, Office 365 mail and other popular services. The framework consists of the Bot Builder SDK, Bot Connector, Developer Portal, and Bot Directory. This framework can be leveraged to create a chat bot that can augment a partner’s support organization and provide customers with a mechanism to perform various self-service operations. </p> <p>The open source project, <a href="https://github.com/Microsoft/Partner-Center-Bot">Partner Center Bot</a>, demonstrates how to leverage the Microsoft Bot Framework to construct a bot that interacts with the Partner Center API. Most customers want the ability to resolve issues on their own, and this project provides a tool that can help fulfill that desire. The intent of this project is not to provide a fully functional bot, but rather to demonstrate how this technology can be utilized to support customers. This initial release has limited features such as the ability to list customers and subscriptions, and answer commonly asked questions through integration with the <a href="https://qnamaker.ai/">QnA Maker</a> service. Additional abilities will be added over time. It is worth mentioning that this project includes a framework that makes it simple to add additional features in a secure manner. </p> <p>In the event there is a feature you would like to see added to the project then please log a feature request <a href="https://github.com/Microsoft/Partner-Center-Bot/issues">here</a>. If you are interested in learning more I would recommend that you check out <a href="https://github.com/Microsoft/Partner-Center-Bot/blob/master/docs/Deployment.md">Deploying</a> and <a href="https://github.com/Microsoft/Partner-Center-Bot/blob/master/docs/AddingIntents.md">Adding New Features</a> to the bot. </p>]]></content:encoded>
    </item>
    <item>
      <title>Partner Center API and Azure Resource Utilization</title>
      <link>https://docs.microsoft.com/archive/blogs/iwilliams/partner-center-api-and-azure-resource-utilization</link>
      <pubDate>Mon, 19 Dec 2016 16:56:00 GMT</pubDate>
      <dc:creator><![CDATA[IsaiahWilliams]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/iwilliams/?p=2185</guid>
      <description><![CDATA[Thanks to the hard work from the Partner Center team we are now able to query Azure usage records...]]></description>
      <content:encoded><![CDATA[Thanks to the hard work from the Partner Center team we are now able to query Azure usage records directly from the Partner Center API. With this update to the Partner Center API we can now perform everything that the CREST API can and a whole lot more! Numerous partners ask how they can generate an itemized invoices for their customer’s Azure consumption. Until now there was not a good way to accomplish this. With this post I want to focus on how to utilize Azure usage records to create an itemized invoice for Azure consumption. The remaining sections of this post will introduce the concepts and provide some sample code. All sample code provided in this post is provided as-is and with no warranty.
<h3>Tags</h3>
Tags in regards to Azure are key value pairs that identity resources with properties that you define. This feature allows us to logically group resource groups and resources within the Azure. You can add tags using the Azure Resource Manager API, command line interface, management portal, PowerShell, or templates. With all of these options to assign tags it is easy to design a provisioning workflow that will ensure all resources have the appropriate tags assigned.

<a href="https://msdnshared.blob.core.windows.net/media/2016/12/AP01.png"><img width="644" height="224" title="AP01" style="padding-top: 0px;padding-left: 0px;margin: 15px 0px;padding-right: 0px;border-width: 0px" alt="AP01" src="https://msdnshared.blob.core.windows.net/media/2016/12/AP01_thumb.png" border="0" /></a>

One of the more common use cases for tags is to generate an itemized invoice. With a direct Azure subscription you could follow the documentation available <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-using-tags#tags-and-billing">here</a>, however, those instructions will not work with Cloud Solution Provider (CSP) subscriptions. Tags applied to resource groups are not inherited by the resources contained with  So in order to construct the invoice correctly you will need to be sure that the appropriate tags have been applied to all of the resources.

In order to demonstrate how tags can be utilized to construct the invoice I have provisioned an instance of App Service and Redis Cache. The instance of App Services has a <em>costCenter</em> tag assigned with a value of <em>Operations</em>, and the instance of Redis Cache has a <em>costCenter</em> tag assigned with a value of <em>Infrastructure.</em>

<a href="https://msdnshared.blob.core.windows.net/media/2016/12/AP02.png"><img width="644" height="220" title="AP02" style="padding-top: 0px;padding-left: 0px;margin: 15px 0px;padding-right: 0px;border-width: 0px" alt="AP02" src="https://msdnshared.blob.core.windows.net/media/2016/12/AP02_thumb.png" border="0" /></a> <a href="https://msdnshared.blob.core.windows.net/media/2016/12/AP03.png"><img width="644" height="220" title="AP03" style="padding-top: 0px;padding-left: 0px;margin: 15px 0px;padding-right: 0px;border-width: 0px" alt="AP03" src="https://msdnshared.blob.core.windows.net/media/2016/12/AP03_thumb.png" border="0" /></a>
<h3>Azure Utilization Records</h3>
Azure utilization records provide information pertaining to the consumption of Azure services. When you supplement these records with details from the Azure RateCard it is possible to calculate the total charges that have been incurred. When we query the Partner Center API for usage records, using the utilization records feature, it returns JSON that is similar to the following
<div class="wlWriterEditableSmartContent" id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:d5660de3-e6f2-46ab-92fc-eab821b873fc" style="float: none;margin: 0px;padding: 0px">

[sourcecode language="java"]
{
  &quot;usageStartTime&quot;: &quot;2016-12-12T16:00:00-08:00&quot;,
  &quot;usageEndTime&quot;: &quot;2016-12-13T16:00:00-08:00&quot;,
  &quot;resource&quot;: {
    &quot;id&quot;: &quot;9cb0bde8-bc0d-468c-8423-a25fe06779d3&quot;,
    &quot;name&quot;: &quot;Standard IO - Table Write Operation Units (in 10,000s)&quot;,
    &quot;category&quot;: &quot;Data Management&quot;,
    &quot;subcategory&quot;: &quot;&quot;
  },
  &quot;quantity&quot;: 0.0146,
  &quot;unit&quot;: &quot;10,000s&quot;,
  &quot;infoFields&quot;: {},
  &quot;instanceData&quot;: {
    &quot;resourceUri&quot;: &quot;/subscriptions/8e4357c6-bd40-4a49-b169-ced26d105d70/resourceGroups/securitydata/providers/Microsoft.Storage/storageAccounts/myaccount&quot;,
    &quot;location&quot;: &quot;ussouthcentral&quot;,
    &quot;partNumber&quot;: &quot;&quot;,
    &quot;orderNumber&quot;: &quot;&quot;
  },
  &quot;attributes&quot;: { &quot;objectType&quot;: &quot;AzureUtilizationRecord&quot; }
}
[/sourcecode]

</div>
As you can use the record being returned contains a wealth of information. If you are using the Partner Center Managed API then it will serialize the response into an object that you can perform operations against. I would like to point out that the resource group name is not one of the properties included with a usage record. However, you can obtain the resource group name with a little bit of work by parse the resourceUri property. The following snippet of code shows one way to obtain all of the usage records for all of the subscription that the specified customer has
<div class="wlWriterEditableSmartContent" id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:05d2e048-d3e6-4b4b-8ae9-ac498a82d2ae" style="float: none;margin: 0px;padding: 0px">

[sourcecode language="csharp"]
/// &lt;summary&gt;
/// Gets all Azure usage records for the specified customer. 
/// &lt;/summary&gt;
/// &lt;param name=&quot;customerId&quot;&gt;Identifier for the customer of interest.&lt;/param&gt;
/// &lt;returns&gt;A collection of Azure usage records.&lt;/returns&gt;
/// &lt;exception cref=&quot;ArgumentException&quot;&gt;
/// customerId
/// &lt;/exception&gt;
public Dictionary&lt;string, List&lt;AzureUtilizationRecord&gt;&gt; GetUsageRecords(string customerId)
{
    Dictionary&lt;string, List&lt;AzureUtilizationRecord&gt;&gt; usageRecords;
    ResourceCollection&lt;Subscription&gt; subscriptions;

    if (string.IsNullOrEmpty(customerId))
    {
        throw new ArgumentException(nameof(customerId));
    }

    try
    {
        subscriptions = PartnerCenter.Customers.ById(customerId).Subscriptions.Get();
        usageRecords = new Dictionary&lt;string, List&lt;AzureUtilizationRecord&gt;&gt;();

        foreach (Subscription s in subscriptions.Items.Where(x =&gt; x.BillingType == BillingType.Usage))
        {
            ResourceCollection&lt;AzureUtilizationRecord&gt; records =
                PartnerCenter.Customers.ById(customerId).Subscriptions.ById(s.Id).Utilization.Azure.Query(
                DateTime.Now.AddMonths(-1), DateTime.Now);

            usageRecords.Add(s.Id, new List&lt;AzureUtilizationRecord&gt;(records.Items));
        }

        return usageRecords;
    }
    finally
    {
        subscriptions = null;
    }
}
[/sourcecode]

</div>
Now that we have a way to obtain Azure usage records the remaining steps in the process, of creating the appropriate invoice, are rather straight forward. With this post I am considering an itemized based invoice as one that groups resources based upon a tag with a key of <em>costCenter. </em>With all of this is in mind we can leverage code similar to the following to generate the invoice for a specific customer.
<div class="wlWriterEditableSmartContent" id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:29ca94fa-b828-4af6-bd58-508170f9b683" style="float: none;margin: 0px;padding: 0px">

[sourcecode language="csharp"]
/// &lt;summary&gt;
/// Generates an itemized based invoice for the speicifed customer.
/// &lt;/summary&gt;
/// &lt;param name=&quot;customerId&quot;&gt;Identifier assign to the customer of interest.&lt;/param&gt;
/// &lt;remarks&gt;
/// This function will group resources based upon the value of the costCenter tag that 
/// is applied to the resource. All resources that do not have a costCenter tag will be 
/// grouped together and reported accordingly.
/// &lt;/remarks&gt;
public void GenerateInvoice(string customerId)
{
    AzureRateCard rateCard;
    Dictionary&lt;string, List&lt;AzureUtilizationRecord&gt;&gt; usageRecords;
    List&lt;AzureUtilizationRecord&gt; costCenterRecords;
    List&lt;AzureUtilizationRecord&gt; records;
    ICollection&lt;string&gt; costCenters;

    if (string.IsNullOrEmpty(customerId))
    {
        throw new ArgumentException(nameof(customerId));
    }

    try
    {
        rateCard = PartnerCenter.RateCards.Azure.Get();
        usageRecords = GetUsageRecords(customerId);

        foreach (string subscription in usageRecords.Keys)
        {
            // Obtain the collection of usage records that either has no costCenter tag or no tags.
            records = usageRecords[subscription].Where(x =&gt; x.InstanceData.Tags == null ||
                x.InstanceData != null &amp;&amp; !x.InstanceData.Tags.ContainsKey(&quot;costCenter&quot;)).ToList();

            // Process the usage records with no tags or no costCenter tag.
            ProcessUsageRecords(rateCard, subscription, string.Empty, records);

            // Obtain the collection of usage records that have a costCenter tag.
            records = usageRecords[subscription].Where(
                x =&gt; x.InstanceData.Tags != null &amp;&amp; x.InstanceData.Tags.ContainsKey(&quot;costCenter&quot;)).ToList();

            // Obtain a list of the cost centers utilized.
            costCenters =
                records.Select(x =&gt; x.InstanceData.Tags)
                    .Where(x =&gt; x.Keys.Contains(&quot;costCenter&quot;))
                    .Select(x =&gt; x.Values).SingleOrDefault();

            if (costCenters == null)
            {
                continue;
            }

            // Process the usage records with a costCenter tag. 
            foreach (string cs in costCenters)
            {
                costCenterRecords = records.Where(x =&gt;
                    x.InstanceData.Tags.Keys.Contains(&quot;costCenter&quot;) &amp;&amp;
                    x.InstanceData.Tags[&quot;costCenter&quot;].Equals(cs, StringComparison.CurrentCultureIgnoreCase)).ToList();

                ProcessUsageRecords(rateCard, subscription, cs, costCenterRecords);
            }
        }
    }
    finally
    {
        costCenterRecords = null;
        rateCard = null;
        records = null;
        usageRecords = null;
    }
}

private void ProcessUsageRecords(AzureRateCard rateCard, string subscription, string costCenter, IEnumerable&lt;AzureUtilizationRecord&gt; usageRecords)
{
    AzureMeter meter;
    decimal quantity = 0, rate = 0, total = 0;

    if (string.IsNullOrEmpty(subscription))
    {
        throw new ArgumentException(nameof(subscription));
    }

    if (usageRecords == null)
    {
        return;
    }

    try
    {
        Console.WriteLine(string.IsNullOrEmpty(costCenter)
            ? &quot;Usage records with no cost center tag...&quot;
            : $&quot;Usage records for {costCenter} cost center...&quot;);

        foreach (AzureUtilizationRecord record in usageRecords)
        {
            // Obtain a reference to the meter associated with the usage record.
            meter = rateCard.Meters.Single(x =&gt; x.Id.Equals(record.Resource.Id));
            // Calculate the billable quantity by substracting the included quantity value.
            quantity = record.Quantity - meter.IncludedQuantity;

            if (quantity &gt; 0)
            {
                // Obtain the rate for the given quantity. Some resources have tiered pricing
                // so this assignment statement will select the appropriate rate based upon the
                // quantity consumed, excluding the included quantity. 
                rate = meter.Rates
                    .Where(x =&gt; x.Key &lt;= quantity).Select(x =&gt; x.Value).Last();
            }

            total += quantity * rate;

            Console.WriteLine(&quot;{0}, {1}&quot;, meter.Name, (quantity * rate));
        }

        Console.WriteLine($&quot;Total Charges {total}&quot;);
    }
    finally
    {
        meter = null;
    }
}
[/sourcecode]

</div>
Since all of the code in this post is considered sample code the invoice data is simply being written to the console. The following figure shows sample when you invoke the code

<a href="https://msdnshared.blob.core.windows.net/media/2016/12/APP01.png"><img width="644" height="338" title="APP01" style="padding-top: 0px;padding-left: 0px;padding-right: 0px;border: 0px" alt="APP01" src="https://msdnshared.blob.core.windows.net/media/2016/12/APP01_thumb.png" border="0" /></a>

In a real world scenario I would recommend that you leverage something like an <a href="https://docs.microsoft.com/en-us/azure/app-service-web/websites-dotnet-webjobs-sdk-get-started">Azure WebJob</a> to generate and store invoices in your particular billing system. Hopefully this information will help you the necessary invoices for your customers.]]></content:encoded>
    </item>
    <item>
      <title>Reseller Storefront</title>
      <link>https://docs.microsoft.com/archive/blogs/iwilliams/reseller-storefront</link>
      <pubDate>Sat, 17 Dec 2016 23:11:06 GMT</pubDate>
      <dc:creator><![CDATA[IsaiahWilliams]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/iwilliams/?p=2115</guid>
      <description><![CDATA[Over the past several weeks I have received numerous questions from various partners regarding the...]]></description>
      <content:encoded><![CDATA[Over the past several weeks I have received numerous questions from various partners regarding the <a href="https://github.com/Microsoft/Partner-Center-Storefront">reseller storefront</a>. This project is a web application that acts as a storefront for Microsoft partners and enables them to sell Microsoft offers to their customers. It can be deployed directory from <a href="https://partnercenter.microsoft.com/">Partner Center</a> or using the Azure Resource Manager (ARM) templates available with the source code. With this post I will be covering how to deploy the reseller storefront using the available ARM templates.
<h3>Prerequisites</h3>
Before attempting to deploy the storefront it is recommended that you gather all of the prerequisite values. The following values will be required in order to deploy the project
<table border="1" cellspacing="0" cellpadding="2">
<tbody>
<tr>
<td valign="top"><strong>Value</strong></td>
<td valign="top"><strong>Description</strong></td>
</tr>
<tr>
<td valign="top">websiteName</td>
<td valign="top">Name assigned to the website in Azure (e.g. cspstorefront.azurewebsites.net)</td>
</tr>
<tr>
<td valign="top">webPortalClientId</td>
<td valign="top">Identifier for the Azure AD application utilized for authentication</td>
</tr>
<tr>
<td valign="top">webPortalClientSecret</td>
<td valign="top">Key for the web portal Azure AD application</td>
</tr>
<tr>
<td valign="top">webPortalAadTenantId</td>
<td valign="top">Tenant identifier where the web portal Azure AD application is provisioned</td>
</tr>
<tr>
<td valign="top">partnerCenterApplicationId</td>
<td valign="top">Identifier for the Azure AD application used to access the Partner Center API</td>
</tr>
<tr>
<td valign="top">partnerCenterApplicationSecret</td>
<td valign="top">Key for the Azure AD application used to access the Partner Center API</td>
</tr>
<tr>
<td valign="top">partnerCenterAadTenantId</td>
<td valign="top">Tenant identifier where the Partner Center Azure AD application is provisioned</td>
</tr>
</tbody>
</table>
<span style="padding-top: 15px;padding-bottom: 15px">
The following sections will walk you through how to obtain the appropriate values for the above parameters
</span>
<h4>Website Name</h4>
The <em>websiteName</em> parameter will be part of the URL assigned to the instance of App Services created in Azure as part of the deployment. If you specify <em>cspstorefront </em>for this parameter then you will access the storefront using <a href="https://cspstorefront.azurewebsites.net">https://cspstorefront.azurewebsites.net</a>. This value must be unique, if you specify a value that is already taken then the deployment will fail. In order to correct the failure specify a different value for the parameter and try again.
<h4>Web Portal Azure AD Application</h4>
Perform the following to create the required Azure AD application
<ol>
 	<li>Browse to the Azure Management portal, <a href="https://portal.azure.com">https://portal.azure.com</a>, and login using credentials that have global admin privileges</li>
 	<li>Open the Azure AD management experience and then click on <em>App registrations</em> –&gt; <em>Add
<a href="https://msdnshared.blob.core.windows.net/media/2016/12/AAD01.png"><img width="644" height="271" title="AAD01" style="margin: 15px 0px;border-width: 0px;padding: 0px 0px 15px 0px" alt="AAD01" src="https://msdnshared.blob.core.windows.net/media/2016/12/AAD01_thumb.png" border="0" /></a></em></li>
 	<li>Complete the new application wizard. The value for the <em>Sign-on URL </em>should match the value you would like to use for the <em>websiteName </em>parameter
<a href="https://msdnshared.blob.core.windows.net/media/2016/12/AAD02.png"><img width="375" height="484" title="AAD02" style="padding-top: 0px;padding-left: 0px;margin: 15px 0px;padding-right: 0px;border-width: 0px" alt="AAD02" src="https://msdnshared.blob.core.windows.net/media/2016/12/AAD02_thumb.png" border="0" /></a></li>
 	<li>Document the <em>Application ID</em> value it will be the <em>webPortalClientId</em><em>
<a href="https://msdnshared.blob.core.windows.net/media/2016/12/AAD03.png"><img width="644" height="286" title="AAD03" style="padding-top: 0px;padding-left: 0px;margin: 15px 0px;padding-right: 0px;border-width: 0px" alt="AAD03" src="https://msdnshared.blob.core.windows.net/media/2016/12/AAD03_thumb.png" border="0" /></a></em></li>
 	<li>Click <em>Required permissions, </em>then click <em>Windows Azure Active Directory (Microsoft.Azure.Active.Directory)</em>, and add the <em>Access the directory as the signed-in user</em> permission
<a href="https://msdnshared.blob.core.windows.net/media/2016/12/AAD05.png"><img width="400" height="484" title="AAD05" style="padding-top: 0px;padding-left: 0px;margin: 15px 0px;padding-right: 0px;border-width: 0px" alt="AAD05" src="https://msdnshared.blob.core.windows.net/media/2016/12/AAD05_thumb.png" border="0" /></a></li>
 	<li>Click on <em>Keys, </em>specify a description, select an appropriate expiration and then click <em>Save</em>
<a href="https://msdnshared.blob.core.windows.net/media/2016/12/AAD04.png"><img width="644" height="161" title="AAD04" style="padding-top: 0px;padding-left: 0px;margin: 15px 0px;padding-right: 0px;border-width: 0px" alt="AAD04" src="https://msdnshared.blob.core.windows.net/media/2016/12/AAD04_thumb.png" border="0" /></a></li>
 	<li>Document the key value, that is displayed after you click <em>Save.</em> This value will be the <em>webPortalClientSecret</em></li>
</ol>
<h4>Partner Center Azure AD Application</h4>
Perform the following to create the Azure AD configured to access the Partner Center API
<ol>
 	<li>Browse to Partner Center, <a href="https://partnercenter.microsoft.com">https://partnercenter.microsoft.com</a>, and login using credentials that have admin agent and global admin privileges</li>
 	<li>Click on the following <em>Dashboard –&gt; Account Settings –&gt; App Management </em></li>
 	<li>Click <em>Add new web app</em> to create the application<a href="https://msdnshared.blob.core.windows.net/media/2016/12/PC01.png"><img width="644" height="200" title="PC01" style="padding-top: 0px;padding-left: 0px;margin: 0px 0px 15px;padding-right: 0px;border-width: 0px" alt="PC01" src="https://msdnshared.blob.core.windows.net/media/2016/12/PC01_thumb.png" border="0" /></a></li>
 	<li>Document the following values
<table border="1" cellspacing="0" cellpadding="2">
<tbody>
<tr>
<td valign="top">Field</td>
<td valign="top">Values</td>
</tr>
<tr>
<td valign="top">App ID</td>
<td valign="top"><em>partnerCenterApplicationId</em></td>
</tr>
<tr>
<td valign="top">Account ID</td>
<td valign="top"><em>webPortalAadTenatId</em> and <em>partnerCenterAadTenantId</em></td>
</tr>
<tr>
<td valign="top">Key</td>
<td valign="top"><em>partnerCenterApplicationSecret</em></td>
</tr>
</tbody>
</table>
<a href="https://msdnshared.blob.core.windows.net/media/2016/12/PC02.png"><img width="644" height="391" title="PC02" style="padding-top: 0px;padding-left: 0px;margin: 0px 0px 15px;padding-right: 0px;border-width: 0px" alt="PC02" src="https://msdnshared.blob.core.windows.net/media/2016/12/PC02_thumb.png" border="0" /></a></li>
</ol>
<h4></h4>
<h4></h4>
<h3>Deploying using Visual Studio</h3>
A deployment project has been included with the source code for the reseller storefront. Before deploying the project you will need an Azure storage account. If you do not already have one you will need to provision a new one before proceeding. To deploy the storefront open the <em>Customer</em>Portal solution, using Visual Studio, and perform the following
<ol>
 	<li>Right click on the <em>CustomerPortal</em> solution in the <em>Solution Explorer</em> and then click <em>Restore NuGet Packages</em></li>
 	<li>Right click on the <em>CustomerPortal.Deployment</em> project then click on Deploy –&gt; New…</li>
 	<li>Complete the <em>Deploy to Resource Group</em> wizard and then click Ok
<a href="https://msdnshared.blob.core.windows.net/media/2016/12/DeploytoResourceGroup.png"><img width="556" height="484" title="DeploytoResourceGroup" style="padding-top: 0px;padding-left: 0px;margin: 15px 0px;padding-right: 0px;border-width: 0px" alt="DeploytoResourceGroup" src="https://msdnshared.blob.core.windows.net/media/2016/12/DeploytoResourceGroup_thumb.png" border="0" /></a></li>
 	<li>Modify the parameters accordingly in the <em>Edit Parameters</em> wizard and then click <em>Save
<a href="https://msdnshared.blob.core.windows.net/media/2016/12/EditParameters.png"><img width="544" height="484" title="EditParameters" style="padding-top: 0px;padding-left: 0px;margin: 15px 0px;padding-right: 0px;border-width: 0px" alt="EditParameters" src="https://msdnshared.blob.core.windows.net/media/2016/12/EditParameters_thumb.png" border="0" /></a></em></li>
 	<li>Review the data in the <em>Output</em> window to confirm the storefront was successfully deployed. Please note this could take a couple of minutes.</li>
</ol>
<h3>Configuring the Storefront</h3>
Now the storefront has been deployed it must be configured. Perform the following in order to configure everything
<ol>
 	<li>Browse to the website (e.g. <a href="https://websiteName.azurewebsites.net">https://websiteName.azurewebsites.net</a>) and login using credentials that belong to the CSP partner that have global admin privileges</li>
 	<li>Complete each of the configuration steps for the storefront to be functional. The storefront will display a message stating it is under construction until all three configuration tasks have been completed<a href="https://msdnshared.blob.core.windows.net/media/2016/12/Portal01.png"><img width="644" height="284" title="Portal01" style="padding-top: 0px;padding-left: 0px;margin: 0px 0px 15px;padding-right: 0px;border-width: 0px" alt="Portal01" src="https://msdnshared.blob.core.windows.net/media/2016/12/Portal01_thumb.png" border="0" /></a></li>
</ol>
Once the storefront configuration has been completed you will be ready to start transacting!

<a href="https://msdnshared.blob.core.windows.net/media/2016/12/Portal02.png"><img width="644" height="283" title="Portal02" style="padding-top: 0px;padding-left: 0px;padding-right: 0px;border-width: 0px" alt="Portal02" src="https://msdnshared.blob.core.windows.net/media/2016/12/Portal02_thumb.png" border="0" /></a>]]></content:encoded>
    </item>
  </channel>
</rss>