<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://kevinritchie.co.uk/feed.xml" rel="self" type="application/atom+xml" /><link href="https://kevinritchie.co.uk/" rel="alternate" type="text/html" /><updated>2026-05-09T11:45:13+00:00</updated><id>https://kevinritchie.co.uk/feed.xml</id><title type="html">Kev Ritchie</title><subtitle>Husband, Father &amp; Coder working primarily on the Microsoft stack and building things in Azure.</subtitle><author><name>Kev Ritchie</name></author><entry><title type="html">Quick Dive into Playwright for .NET</title><link href="https://kevinritchie.co.uk/testing/2021/06/04/quick-dive-into-playwright-for-dotnet.html" rel="alternate" type="text/html" title="Quick Dive into Playwright for .NET" /><published>2021-06-04T20:36:06+00:00</published><updated>2021-06-04T20:36:06+00:00</updated><id>https://kevinritchie.co.uk/testing/2021/06/04/quick-dive-into-playwright-for-dotnet</id><content type="html" xml:base="https://kevinritchie.co.uk/testing/2021/06/04/quick-dive-into-playwright-for-dotnet.html"><![CDATA[<p>One of the items from this year’s Microsoft Build Conference that stood out for me was Playwright during <a href="https://www.youtube.com/watch?v=EWYYgEkGJfs" target="_blank">Scott Hanselman and Friends Keynote</a>.</p>

<p>Playwright is a Headless Browser testing framework that enables fast, reliable end-to-end testing across all modern browsers.</p>

<p>If I can automate something, count me in.</p>

<p>I have used Selenium UI in the past but thought I would take a quick look at Playwright and see how easy it was to implement in a brand-new project.</p>

<p><strong>The Walkthrough</strong></p>

<p>This quick walkthrough will take you through the steps I took to get Playwright working in an xUnit Test project and running a test against a Blazor Server App.</p>

<p>First, we need to create our Blazor Server App.  Open Visual Studio 2019, select “Create a new project” on the Start Window, select Blazor Server App and click Next.  Give the project a name, click Next, keep the defaults selected and click Create.</p>

<p><em>Note: I’m running version 16.10 of VS 2019 and using .NET 5.</em></p>

<p>This will generate a new Blazor Server App with a default template that includes the Counter page and “Click me” button.</p>

<p><img src="/images/CounterPage.jpg" alt="Blazor Server App Counter Page" /></p>

<p>Next, add a Test project to your solution.  In my case, I added an xUnit project as that is what I’m most familiar with.</p>

<p>To do this, right click on your solution and “Add -&gt; New Project…”. Select xUnit Test Project and click Next.  Again, give the project a name, click Next, keep the defaults selected and click Create.</p>

<p>If you wish, you can rename the class that’s already added to your project.  Something like PlaywrightDemoTests.</p>

<p>For our next step, we need to add the PlaywrightSharp Nuget package to the newly created xUnit Test project.
Right click on your Test project and Manage Nuget Packages.  Search for PlaywrightSharp and install the latest version.</p>

<p>Once that’s installed, we’re in a position to run our Blazor Server App and the Playwright Inspector.</p>

<p>The Blazor Server App should already be set as the Start-up Project, so hit F5 to spin it up.</p>

<p>Once it’s running, make a note of the URL.  In my case, it was https://localhost:44379/.  We need to make sure the application is running so that Playwright can connect to it.</p>

<p>Now, we need to get the Playwright Inspector running.</p>

<p>Within Visual Studio, go to View and select Terminal.  This will load up the Developer PowerShell.
Run the following command, “npx playwright codegen https://localhost:44379/” (You will need to make sure Node.js 12 or above is installed).</p>

<p>This will spin up another copy of your Blazor Server App and run the Playwright Inspector.</p>

<p>In this copy of the Blazor Server App, navigate to the counter page.</p>

<p>Immediately you will see Playwright recording the steps and refactoring the code as it monitors your actions.</p>

<p>We can now take that generated code and put it our test project.  Make sure to change the output to C#.
In this example I have change the browser to be headless.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Fact]
public async Task CanNavigateToTheCounterPage()
{
	await Playwright.InstallAsync();

	using var playwright = await Playwright.CreateAsync();

	await using var browser = await playwright.Chromium.LaunchAsync(
		headless: true
	);

	var context = await browser.NewContextAsync();

	// Open new page
	var page = await context.NewPageAsync();

	// Go to https://localhost:44379/
	await page.GoToAsync("https://localhost:44379/");

	// Click text=Counter
	await page.ClickAsync("text=Counter");

	Assert.Equal("https://localhost:44379/counter", page.Url);
}
</code></pre></div></div>

<p>A super simple test I know, but this is more about the ease of using Playwright and getting the automated tests into your Test project.</p>

<p>To run the test, we need to run the Blazor Server App and then from the Developer PowerShell window, run the command dotnet test – <em>I’m sure there is a way to run this test and get it to run the localhost application without having to spin it up manually.  I’ll provide an update when I find it</em>.</p>

<p>You should see the test start to run and if all is well, you should see a passing test.</p>

<p><img src="/images/PlaywrightPassingTest.jpg" alt="Playwright Passing Test" /></p>

<p>In summary, it was relatively easy to get this up and running and there are a multitude of scenarios you can use Playwright to test.</p>

<p>To take this further, there is a great deal of documentation at <a href="https://playwright.dev/dotnet/docs/intro/" target="_blank">https://playwright.dev/dotnet/docs/intro/</a> to help you on your way and get the most out of the framework.</p>

<p>The great thing is you can add this to your Continuous Integration environments too <a href="https://playwright.dev/docs/ci" target="_blank">https://playwright.dev/docs/ci</a>.</p>]]></content><author><name>Kev Ritchie</name></author><category term="testing" /><category term="Testing" /><summary type="html"><![CDATA[One of the items from this year’s Microsoft Build Conference that stood out for me was Playwright during Scott Hanselman and Friends Keynote.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kevinritchie.co.uk/images/PlaywrightLogo.png" /><media:content medium="image" url="https://kevinritchie.co.uk/images/PlaywrightLogo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Fixing a SSRS Report Drillthrough Issue</title><link href="https://kevinritchie.co.uk/ssrs/reporting/2021/04/09/ssrs-drillthrough-not-working.html" rel="alternate" type="text/html" title="Fixing a SSRS Report Drillthrough Issue" /><published>2021-04-09T14:48:06+00:00</published><updated>2021-04-09T14:48:06+00:00</updated><id>https://kevinritchie.co.uk/ssrs/reporting/2021/04/09/ssrs-drillthrough-not-working</id><content type="html" xml:base="https://kevinritchie.co.uk/ssrs/reporting/2021/04/09/ssrs-drillthrough-not-working.html"><![CDATA[<p>It’s not uncommon to have a SQL Server Reporting Services (SSRS) Report with a drillthrough report and for this particular project I’ve been working on, this was a requirement.</p>

<p>To give a bit of background.  The application itself is a Blazor Server App which includes an iFrame pointing to a ASP.NET Web Application hosting the SSRS Report Viewer control to serve up the reports.  The reason for this approach is that the SSRS Report Viewer control does not work with Blazor.</p>

<p>Everything was working great until we noticed that you couldn’t drill through to other reports. Gah!</p>

<p>What could be the problem?</p>

<p>First thoughts were, could it be an issue with the iFrame?  A quick test running the report directly in the ASP.NET Web Application produced the same issue!  So it’s not the iFrame.</p>

<p>Turns out the resolution is a simple one…</p>

<p>…wrap the code that renders the report in an <code class="language-plaintext highlighter-rouge">!IsPostback</code> in the page load of the hosting application.  Hey presto, drill through now works.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        rvDashboardReports.ServerReport.ReportPath = "/";

        Uri uri = new Uri(Properties.Settings.Default.ReportServerURL);

        rvDashboardReports.ServerReport.ReportServerUrl = uri;
        rvDashboardReports.ServerReport.Refresh();
    }
}
</code></pre></div></div>

<p>Sometimes it’s the easy fixes that can keep you scratching your head thinking, what have I done wrong?!</p>]]></content><author><name>Kev Ritchie</name></author><category term="ssrs" /><category term="reporting" /><category term="SSRS" /><summary type="html"><![CDATA[It’s not uncommon to have a SQL Server Reporting Services (SSRS) Report with a drillthrough report and for this particular project I’ve been working on, this was a requirement.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kevinritchie.co.uk/images/SSRSLogo.png" /><media:content medium="image" url="https://kevinritchie.co.uk/images/SSRSLogo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Setting your ASP.NET Core Environment on Deployment (Right Click Publish) 🐱‍🏍</title><link href="https://kevinritchie.co.uk/aspnet/core/environment/2021/04/07/aspnetcore-environment-variable.html" rel="alternate" type="text/html" title="Setting your ASP.NET Core Environment on Deployment (Right Click Publish) 🐱‍🏍" /><published>2021-04-07T13:41:06+00:00</published><updated>2021-04-07T13:41:06+00:00</updated><id>https://kevinritchie.co.uk/aspnet/core/environment/2021/04/07/aspnetcore-environment-variable</id><content type="html" xml:base="https://kevinritchie.co.uk/aspnet/core/environment/2021/04/07/aspnetcore-environment-variable.html"><![CDATA[<p>So you’ve “Right Click Published” your ASP.NET Core application and things are going great.  Suddenly you hit an error on the site with a message similar to this:</p>

<p><strong>The Development environment shouldn’t be enabled for deployed applications. It can result in displaying sensitive information from exceptions to end users. For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development and restarting the app.</strong></p>

<p>You’ve double checked the publish profile settings and everything looks good; you can see it’s in release mode.  What gives?!</p>

<p>Well, when the host is built, it remembers the last environment setting it read.  In the case of your local development, it’ll be reading from the launchSettings.json file and thus would have been set to <strong>Development</strong>.</p>

<p><strong>How do we fix that for Deployment?</strong></p>

<p>There are a number of ways to fix this on deployment and I’ve included some useful links below to provide more depth on the subject and the many ways you can deploy your application.</p>

<p>In our case, we’re using “Right Click Publish” method from within Visual Studio.</p>

<p>To set the <code class="language-plaintext highlighter-rouge">ASPNETCORE_ENVIRONMENT</code> variable on deployment, we need to edit the Publish Profile (.pubxml) file found under the Properties-&gt;PublishProfiles folder of your project.  For our example, this is the <code class="language-plaintext highlighter-rouge">IISProfile.pubxml</code> file.</p>

<p>All we need to do is add the <code class="language-plaintext highlighter-rouge">&lt;EnvironmentName&gt;</code> property to the file and set it to Production.  There will be other properties in the file, you can add this property as the last item in the <code class="language-plaintext highlighter-rouge">PropertyGroup</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;PropertyGroup&gt;
  &lt;EnvironmentName&gt;Production&lt;/EnvironmentName&gt;
&lt;/PropertyGroup&gt;
</code></pre></div></div>
<p><em>Out of the box you can set the property to Development, Staging or Production - you can even add custom ones.  More details can be found in the useful links below.</em></p>

<p>Don’t forget to save!</p>

<p>When you next Publish your application the ASPNETCORE_ENVIRONMENT variable will be added to the web.config file and set to Production.</p>

<p>You should see something like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;environmentVariables&gt;
    &lt;environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" /&gt;
&lt;/environmentVariables&gt;
</code></pre></div></div>

<p><strong>Useful Links</strong></p>

<p>The Publish Profile environment documentation can be found <a href="https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/visual-studio-publish-profiles?view=aspnetcore-3.1#set-the-environment" target="_blank">here</a>.</p>

<p>For other platforms (including Azure), additional information on Environments and setting the Environment variable can be found <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-3.1#environments" target="_blank">here</a>.</p>]]></content><author><name>Kev Ritchie</name></author><category term="aspnet" /><category term="core" /><category term="environment" /><category term="ASP.NET Core" /><summary type="html"><![CDATA[So you’ve “Right Click Published” your ASP.NET Core application and things are going great. Suddenly you hit an error on the site with a message similar to this:]]></summary></entry><entry><title type="html">Override the Login Page in a Blazor Server App</title><link href="https://kevinritchie.co.uk/blazor/server/identity/2021/03/25/override-login-page-blazor-server-app.html" rel="alternate" type="text/html" title="Override the Login Page in a Blazor Server App" /><published>2021-03-25T18:57:06+00:00</published><updated>2021-03-25T18:57:06+00:00</updated><id>https://kevinritchie.co.uk/blazor/server/identity/2021/03/25/override-login-page-blazor-server-app</id><content type="html" xml:base="https://kevinritchie.co.uk/blazor/server/identity/2021/03/25/override-login-page-blazor-server-app.html"><![CDATA[<p>I’ll admit this one had me stumped.</p>

<p>When you create a Blazor Server App and add authorisation out of the box, you get some really neat baked in pages e.g. Login, Registration, Log Out etc., that takes the pain out of you having to code the entire user registration, authentication and authorisation process.</p>

<p>This is a fantastic feature and saves a tonne of time.</p>

<p>However, I was caught out a little.  I wanted to customise the Login page and remove some of the layout, but I couldn’t find it in the Identity/Accounts folder.  How do we fix that?</p>

<p>If you right click on your project and choose New -&gt; Add Scaffolded Item, then choose Identity from the menu on the left.</p>

<p><img src="/images/AddNewScaffoldedItem.jpg" alt="Adding a new Scaffolded Item to your Blazor Server App" /></p>

<p>You’ll get a list of pages you can override in the solution.</p>

<p><img src="/images/AddIdentity.jpg" alt="Overriding Identity Pages in your Blazor Server App" /></p>

<p>Here you can choose to overwrite them all or just choose the one you’re interested in.</p>

<p>You’ll then be able to customise any of the pages you choose.</p>

<p>Be carfeul though, if you choose to override the Log Out page, you will run into some issues.  A resolution to this can be found <a href="https://github.com/dotnet/aspnetcore/issues/17839" target="_blank">here</a>.</p>

<p>Need to know more about Blazor authentication and authorisation?  Check out this <a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-5.0" target="_blank">page</a> over on Microsoft Docs.</p>]]></content><author><name>Kev Ritchie</name></author><category term="blazor" /><category term="server" /><category term="identity" /><category term="Blazor" /><summary type="html"><![CDATA[I’ll admit this one had me stumped.]]></summary></entry><entry><title type="html">Microsoft Graph - First Steps</title><link href="https://kevinritchie.co.uk/azure/microsoft/graph/app/registrations/2020/11/22/first-steps-with-microsoft-graph.html" rel="alternate" type="text/html" title="Microsoft Graph - First Steps" /><published>2020-11-22T22:27:31+00:00</published><updated>2020-11-22T22:27:31+00:00</updated><id>https://kevinritchie.co.uk/azure/microsoft/graph/app/registrations/2020/11/22/first-steps-with-microsoft-graph</id><content type="html" xml:base="https://kevinritchie.co.uk/azure/microsoft/graph/app/registrations/2020/11/22/first-steps-with-microsoft-graph.html"><![CDATA[<p>I’ve been interested in Microsoft Graph for some time now but never really had an opportunity to work it into a customer solution.</p>

<p>Recently an opportunity presented itself to put it to use.  Creating a visitor booking in application.
The application was built using Blazor (Server Side) with Telerik components and has a SQL Server Databases on the backend to store visit information.</p>

<p>The requirement was a simple one.  Record some visitor information, agreement to Health &amp; Safety policy, select an employee and notify that employee that a visitor has arrived to see them.
As this blog post is about Microsoft’s Graph, I’ll keep the details to just that.</p>

<p><strong>Getting Started</strong></p>

<p>To get started we’ll need to download the Microsoft.Identity.Client Nuget package into our project.  This will allow us to create the ConfidentialClientApplicationBuilder to generate a token to pass in the request headers when we communicate with the Graph.</p>

<p>Next, we’ll need to store a reference to our authentication authority (https://login.microsoftonline.com/), Tenant ID, Client ID and Client Secret.  For the purpose of this application as it’s internal to us, we’ll store the details in our appSettings.json file.  For any customer-based applications we would be storing these details in the <a href="https://azure.microsoft.com/en-gb/services/key-vault/" target="_blank">Azure Key Vault</a>.</p>

<p>To get the Tenant ID, Client ID and Client Secret details we’ll need to register a new App Registration in the Azure Portal.</p>

<p><strong>App Registrations</strong></p>

<p>To register a new App Registration, at the top of the Portal search for the App Registrations resource, select it and then click “+ New Registration”.</p>

<p>You’ll then need to enter a few details.</p>

<p>We’ll give our App Registration a name e.g. “My Booking In App”.</p>

<p>As this application is for internal use only, we’ll leave the default of “Accounts in this organizational directory only (Single tenant)”.</p>

<p>For the Redirect URI, we’ll add in https://localhost/ for now.  This will help whilst we test the application in development.  The Redirect URI is the path that you want to come back to when you have successfully authenticated.</p>

<p><img src="/images/BookingAppRegistrations.jpg" alt="Creating a new App Registration" /></p>

<p>Now that we have our App Registration, there are a few more steps to get us ready for connecting to the Graph.</p>

<p>Within the App Registration, we’ll select Authentication.  Here we need to make sure that the “ID tokens” is checked under Implicit grant.</p>

<p>Next, we’ll need to create a Client Secret.  Under Certificates &amp; secrets, click “+ New client secret.  Give the secret a description, set the expiration value; in our case we selected “Never”.  Clicking Add will generate a new secret for your App Registration.  Make sure to note this down as you won’t be able to view it again when you move away from the page.</p>

<p>Finally, we’ll configure the API we wish to utilise for our application.  In this case, Microsoft Graph.</p>

<p>Under API Permissions, select “+ Add a permission”.</p>

<p>Select Microsoft Graph.</p>

<p>You’ll then be asked to choose what type of permission your application requires; either Delegated (accessing the API as a signed-in user) or Application (no signed-in user).  As our application will be standalone, we’ll select Application permissions.</p>

<p>In this instance we only need access to the Users’ full profiles, so let’s filter the list by User.  Under the User option.  Select “User.Read.All”.  This is all we need for this application.  Admin consent will be needed for this permission.  All you need to do is click “Grant admin consent for…” – you will need admin privileges within the tenant to do this.</p>

<p><img src="/images/BookingApplicationPermissions.jpg" alt="Setting the API permissions" /></p>

<p>With that done, we’ll make note of our Tenant (Directory) ID and Client (Application) ID.  You’ll find this under the Overview section of the App Registration.  Now with our authentication authority (Instance), Tenant ID, Client ID and Secret, we’ll add those to our appSetting.json file.</p>

<p><img src="/images/BookingAppSettings.jpg" alt="App Registration Details in App Settings" /></p>

<p><strong>How to connect to the Graph</strong></p>

<p>For this application we used the HTTP Client to connect to the Graph.</p>

<p>First, we’ll need access to our appSettings.json file to get our Tenant ID etc.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>IConfigurationRoot configuration = new ConfigurationBuilder()
                .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
                .AddJsonFile("appsettings.json")
                .Build();

var auth = configuration.GetSection("AzureAd").GetSection("Instance").Value;
var tenantId = configuration.GetSection("AzureAd").GetSection("TenantId").Value;
var clientId = configuration.GetSection("AzureAd").GetSection("ClientId").Value;
var clientSecret = configuration.GetSection("AzureAd").GetSection("ClientSecret").Value;
</code></pre></div></div>

<p>We’ll then generate our Authentication URI which will be used in our ConfidentialClientApplicationBuilder.</p>

<p><code class="language-plaintext highlighter-rouge">Uri authority = new Uri($"{auth}{tenantId}");</code></p>

<p>We’ll now create our ConfidentialClientApplicationBuilder to help generate our token to pass in the headers of our HTTP request.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>IConfidentialClientApplication confidentialClient = ConfidentialClientApplicationBuilder
                    .Create(clientId)
                    .WithAuthority(authority)
                    .WithClientSecret(clientSecret)
                    .Build();
</code></pre></div></div>

<p>Next, we need to set our scopes.  For this we’ll just set the scope to .default.  As per Microsoft’s Docs – “A scope value of https://graph.microsoft.com/.default is functionally the same as the v1.0 endpoints resource=https://graph.microsoft.com - namely, it requests a token with the scopes on Microsoft Graph that the application has registered for in the Azure portal”.</p>

<p><code class="language-plaintext highlighter-rouge">var scopes = new string[] { "https://graph.microsoft.com/.default" };</code></p>

<p>We can now request our Access Token.</p>

<p><code class="language-plaintext highlighter-rouge">AuthenticationResult result = await confidentialClient.AcquireTokenForClient(scopes).ExecuteAsync();</code></p>

<p>With the token in hand, we can now create a new HTTP Client.</p>

<p><code class="language-plaintext highlighter-rouge">HttpClient _httpClient = new HttpClient();</code></p>

<p>With our new HTTP Client, we’ll pass the token in the header of the request.</p>

<p><code class="language-plaintext highlighter-rouge">_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);</code></p>

<p>From there, we’ll now be able to call into the Graph and call the APIs we need.</p>

<p>For our application we needed the Direct Reports of a Manager (details in the resources below) and the photos of those Direct Reports (also detailed in the resources below).</p>

<p>To get the Direct Reports we call:</p>

<p><code class="language-plaintext highlighter-rouge">var managerReportsRequest = await _httpClient.GetAsync("https://graph.microsoft.com/v1.0/users('" + managerEmail + "')/directReports");</code></p>

<p>We’ll then read out and parse the response to get all the Employees in our company:</p>

<p><code class="language-plaintext highlighter-rouge">var managerReportsData = System.Text.Json.JsonDocument.Parse(await managerReportsRequest.Content.ReadAsStreamAsync());</code></p>

<p><code class="language-plaintext highlighter-rouge">var userArray = managerReportsData.RootElement.GetProperty("value").EnumerateArray();</code></p>

<p>Looping through the employees (userArray) we can then pull out details like Id, displayName, mail e.g. GetProperty(“id”).GetString();</p>

<p>With the Id, we can then do things like, grab the employee’s photo to show on the Select Employee screen:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var photoRequest = await _httpClient.GetAsync("https://graph.microsoft.com/v1.0/users('" + userId + "')/photo/$value");

if (photoRequest.IsSuccessStatusCode)
{
    var photo = await photoRequest.Content.ReadAsByteArrayAsync();

    if (photo != null)
    {
        string base64String = Convert.ToBase64String(photo, 0, photo.Length);

        emp.EmployeePhoto = string.Format("data:image/png;base64,{0}", base64String);
    }
}
</code></pre></div></div>

<p><strong>Notifying the Employee</strong></p>

<p>To notify the employee that a visitor had arrived we used Power Automate and Flow Bot for Teams to send an @mention message into a Teams Channel.  If anyone is interested in how we did this, you can DM me on Twitter or drop me an email at kev-the-dev@outlook.com.</p>

<p><strong>Summary</strong></p>

<p>In summary, connecting into Microsoft’s Graph was real easy and there is a vast amount of documentation on it at https://docs.microsoft.com to help you get started.  I just thought it useful to document our dalliance with the Graph.</p>

<p>Am I a Graph believer?  You bet I am.  The possibilities are endless, and we’ve already built a holiday/vacation Blazor application too that hooks into the Graph!</p>

<p><strong>Resources</strong></p>

<p><a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app" target="_blank">Register an application with the Microsoft Identity Platform</a></p>

<p><a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-configure-app-access-web-apis" target="_blank">Configure a client application to access a Web API</a></p>

<p><a href="https://docs.microsoft.com/en-us/graph/api/user-list-directreports?view=graph-rest-1.0&amp;tabs=http" target="_blank">List Direct Reports</a></p>

<p><a href="https://docs.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0" target="_blank">Get Photo</a></p>]]></content><author><name>Kev Ritchie</name></author><category term="azure" /><category term="microsoft" /><category term="graph" /><category term="app" /><category term="registrations" /><category term="Microsoft Graph" /><summary type="html"><![CDATA[I’ve been interested in Microsoft Graph for some time now but never really had an opportunity to work it into a customer solution.]]></summary></entry><entry><title type="html">Passed AZ-400 Azure DevOps Engineer Expert</title><link href="https://kevinritchie.co.uk/azure/devops/certifications/2020/10/26/passed-azure-devops-engineer-expert.html" rel="alternate" type="text/html" title="Passed AZ-400 Azure DevOps Engineer Expert" /><published>2020-10-26T14:48:42+00:00</published><updated>2020-10-26T14:48:42+00:00</updated><id>https://kevinritchie.co.uk/azure/devops/certifications/2020/10/26/passed-azure-devops-engineer-expert</id><content type="html" xml:base="https://kevinritchie.co.uk/azure/devops/certifications/2020/10/26/passed-azure-devops-engineer-expert.html"><![CDATA[<p><strong>This was a tough one!</strong></p>

<p>So having already passed AZ-203 earlier in the year and after weeks of trying to cram in a load of study, I managed to pass another online proctored Microsoft Certification this year, AZ-400 Azure DevOps Engineer Expert.</p>

<p>What I will say about this exam is that you definitely need to know more than just Azure and Azure DevOps.  You have to have a good understanding DevOps processes, CI/CD pipelines, GitHub and other things like Jenkins, Octopus Deploy, Chef, Puppet, Terraform and YAML to name a few.</p>

<p>Here are the resources that I found useful:</p>

<ul>
  <li><a href="https://docs.microsoft.com/en-us/learn/certifications/devops-engineer" target="_blank">MS Learn</a></li>
  <li><a href="https://partner.microsoft.com/en-us/training/assets/collection/azure-devops-engineer-expert-certification-exam-az-400#/" target="_blank">MS Partner Network Training</a></li>
  <li><a href="https://www.pluralsight.com/paths/microsoft-azure-devops-engineer-az-400" target="_blank">Pluralsight</a></li>
</ul>

<p>The MS Learn link lays out the prerequisites and skills needed to pass this exam.  What I would say, is try and get as much hands on experience as you can.  Nothing can beat that.  The MS Learn courses do have Sandboxes which is a great feature.  And if you don’t have an Azure Subscription, you can get 12 months of free services <a href="https://azure.microsoft.com/en-gb/free/" target="_blank">here</a>.</p>

<p><img src="/images/DevOpsEngineerExpert.png" alt="Azure DevOps Engineer Expert" /></p>]]></content><author><name>Kev Ritchie</name></author><category term="azure" /><category term="devops" /><category term="certifications" /><category term="Certification" /><summary type="html"><![CDATA[This was a tough one!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kevinritchie.co.uk/images/DevOpsEngineerExpert.png" /><media:content medium="image" url="https://kevinritchie.co.uk/images/DevOpsEngineerExpert.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Azure DevOps - Build Failed: project.assets.json missing</title><link href="https://kevinritchie.co.uk/azure/devops/build/pipeline/agent/pool/2020/08/01/azure-devops-build-failed-project-assets-json.html" rel="alternate" type="text/html" title="Azure DevOps - Build Failed: project.assets.json missing" /><published>2020-08-01T00:19:42+00:00</published><updated>2020-08-01T00:19:42+00:00</updated><id>https://kevinritchie.co.uk/azure/devops/build/pipeline/agent/pool/2020/08/01/azure-devops-build-failed-project-assets-json</id><content type="html" xml:base="https://kevinritchie.co.uk/azure/devops/build/pipeline/agent/pool/2020/08/01/azure-devops-build-failed-project-assets-json.html"><![CDATA[<p><strong>The one thing you dread seeing…Azure DevOps [Build failed]</strong></p>

<p>For the last few hours I have been trying to sort a problem with a build that started failing out of the blue.  The solution compiled and ran locally with success and previous pipeline builds completed successfully.</p>

<p><img src="/images/ProjectAssetsFail.png" alt="Azure DevOps Build Failed Message" /></p>

<p>The build was suddenly complaining about the project.assets.json file and that I had to run a NuGet package restore which would create the file for me.</p>

<p>I already had a NuGet Restore step in my pipeline, so what was the problem?  Why wasn’t it restoring the files?</p>

<p>In an attempt to force the restore, I added a <strong>/t:restore</strong> parameter to the MSBuild task.  I could see that the NuGet feeds were being interogated and files downloaded and installed, but it would fail trying to restore a package from the Telerik NuGet feed.  Strange, as running a restore locally worked OK.</p>

<p>Checking one of the projects I could see that a Telerik package had an update available and it did relate to the project that was having the problem.  I upgraded the package and checked the solution back in to kick off the build pipeline.  It subsequently failed again with the same problem!</p>

<p>During my investigation, I noticed that our Hosted Agent was set to <strong>VS2017</strong>, which has since been deprecated.</p>

<p>Best to change that.</p>

<p>I changed the Agent Pool to <strong>Azure Pipelines</strong> and set the Agent Specification to <strong>windows-2019</strong></p>

<p><img src="/images/AzureDevOpsAgentPool.png" alt="Azure DevOps Agent Pool" /></p>

<p>You can see a list of the available virtual machine images here (Agent Specifications) <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops&amp;tabs=yaml#software" target="_blank">here</a>.</p>

<p>I also removed the <strong>/t:restore</strong> parameter from the MSBuild task that I previously added.  I was certain this wasn’t the problem.</p>

<p>Saving and queuing up a new build, I waited for the MSBuild task to complete.</p>

<p>Success!</p>

<p>I suspect the problem was down to the local NuGet package cache on the Virtual Agent needing a bit of a kick.</p>

<p>If anyone else comes across the problem, I hope this helps.</p>]]></content><author><name>Kev Ritchie</name></author><category term="azure" /><category term="devops" /><category term="build" /><category term="pipeline" /><category term="agent" /><category term="pool" /><category term="DevOps" /><summary type="html"><![CDATA[The one thing you dread seeing…Azure DevOps [Build failed]]]></summary></entry><entry><title type="html">Using Azure DevOps and Power Automate to create a basic Report</title><link href="https://kevinritchie.co.uk/azure/devops/reporting/power/automate/2020/07/03/using-azure-devops-and-power-automate.html" rel="alternate" type="text/html" title="Using Azure DevOps and Power Automate to create a basic Report" /><published>2020-07-03T20:39:52+00:00</published><updated>2020-07-03T20:39:52+00:00</updated><id>https://kevinritchie.co.uk/azure/devops/reporting/power/automate/2020/07/03/using-azure-devops-and-power-automate</id><content type="html" xml:base="https://kevinritchie.co.uk/azure/devops/reporting/power/automate/2020/07/03/using-azure-devops-and-power-automate.html"><![CDATA[<p><strong>So, you’ve got some data in your Azure DevOps project that you’d like to get out and turn into a bespoke report?</strong></p>

<p>Our requirement was fairly basic, to produce a simple report where customers can see when certain features are going to be released and what is being worked on in a particular Sprint.</p>

<p>The team had created a Query within Azure DevOps that listed out the Sprints/Iterations, Work Items and their State.  The only difficulty was getting that information out into an On-Premise SQL Server database.  The only obvious route within Azure DevOps was to export the results as a CSV file and then import it into SQL…not great!</p>

<p>Surely there had to be a way to “Automate” this?</p>

<p>Step in Power Automate.</p>

<p><strong>How did we do it?</strong></p>

<p>We started with creating a Scheduled flow from blank, giving it an name and setting the frequency of when the flow would trigger.  If you leave the Flow name blank, one will be created for you.</p>

<p><img src="/images/ScheduledFlowDevOps.png" alt="Create a Scheduled Flow from blank" /></p>

<p>You’ll get your first step automatically created for you called Recurrence.  You can click on this step and change the frequency at any time.</p>

<p>To get the Query results from Azure DevOps, we inserted a new step and added a new Action.  We then searched connectors and actions for Azure DevOps and scrolled through the list and selected <strong>Get query results</strong>.  You can click on Azure DevOps and this will filter the list of results to the triggers and actions available for Azure DevOps.</p>

<p><img src="/images/ActionAzureDevOpsQuery.png" alt="Select your Azure DevOps Query" /></p>

<p>Here it was just a matter of entering the Organisation Name and Project Name.  You’ll be asked to authenticate with Azure DevOps in order to list out the queries within your project.  You’ll have access to your queries and any shared queries.</p>

<p>Next up was interacting with the On-Premise SQL Server where the data was being stored.</p>

<p>In order to access the On-Premise SQL Server we needed to create a data gateway.  You can find out how to install and configure the data gateway <a href="https://docs.microsoft.com/en-us/power-automate/gateway-reference" target="_blank">here</a>.</p>

<p>Once that was set up and configured, we could then create a new connection to our SQL Server.  After clicking <strong>”+ New connection”</strong> within Data -&gt; Connections, we chose SQL Server.  From there we set the appropriate SQL Authentication credentials, database details and selected the data gateway created previously.</p>

<p>Back in our Flow, we added a new step for a SQL Server action and selected the <strong>Execute stored procedure (V2)</strong> action.  We already had a stored procedure which cleared down the table we would be interacting with, so selected the connection we just created and picked our stored procedure.</p>

<p>The next step was to process the Azure DevOps query results.  For this step we used the SQL Server <strong>Insert row (V2)</strong> action.</p>

<p><img src="/images/ProcessAzureDevOpsQuery.png" alt="Process Azure DevOps query" /></p>

<p>This next bit is the bit I love about Power Automate; <strong>Dynamic Content</strong>.  This dynamically picks up references to fields/outputs from previous steps and also allows you to create expressions with that content.</p>

<p>As you can see from above, for the output from the previous step we selected the <strong>value</strong> which is the result of the Azure DevOps query.</p>

<p>The insert row action presents us with the structure of our table where we can map the relevent fields from the query results to our table columns.</p>

<p>You’ll notice that we’ve not set the Iteration Start and End Date.  The reason being that we couldn’t seem to get to those dates via the query <em>(can this be done?)</em>.  So what to do?</p>

<p><strong>Step in Azure DevOps - Analytics</strong></p>

<p>You can find an explanation of Analytics and what it can do <a href="https://docs.microsoft.com/en-us/azure/devops/report/powerbi/what-is-analytics?view=azure-devops" target="_blank">here</a>.  The bit we’re interested in is OData queries.</p>

<p>In order to get the data out via Analytics we needed to create a Personal Access Token in Azure DevOps.</p>

<p><img src="/images/PAT.png" alt="Creating a Personal Access Token" /></p>

<p>Here we created a <strong>+New Token</strong>, giving it an name and setting the Organisation and Expiration details as required.</p>

<p>Next we set the Scope of the Token.</p>

<p>We left it as a custom defined Scope, clicked <strong>Show all scopes</strong>, checked <strong>Read</strong> against Analytics and clicked Create.</p>

<p><img src="/images/PATScope.png" alt="Creating a Personal Access Token Scope" /></p>

<p>Make sure to note your new token as you’ll need it later.</p>

<p>Now we have our Personal Access Token, we can create a new step in our Flow and add a HTTP action to get the OData from Analytics.</p>

<p><em>The following <a href="https://docs.microsoft.com/en-us/azure/devops/report/extend-analytics/wit-analytics?view=azure-devops" target="_blank">link</a> provides guidance on how to create OData queries</em>.</p>

<p><img src="/images/HTTPRequestOData.png" alt="Getting Azure DevOps Analytics OData" /></p>

<p>The main point to note here is that you will need to set some basic authentication credentials in advanced options, which are defined below:</p>

<ol>
  <li><strong>Authentication</strong> = “basic”</li>
  <li><strong>User</strong> = “user” or “test”</li>
  <li><strong>Password</strong> = [The Personal Access Token you created earlier]</li>
</ol>

<p>In the next step of the Flow, we added an action to Parse the JSON result from the OData query.</p>

<p><img src="/images/ParseJSONAction.png" alt="Parse JSON Action" /></p>

<p>Using dynamic content, we selected the <em>Body</em> from the previous step as our Content for this step.</p>

<p>To get the Schema, we used <strong>Generate from sample</strong> and pasted in the data returned from the OData query.  You can get this by running the query through Postman or browser.</p>

<p><img src="/images/ParseJSONData.png" alt="Parse JSON Data" /></p>

<p><strong>Now to push that data into our On-Premise SQL Server.</strong></p>

<p>In our final step, we added an action for <strong>Apply to each</strong>.  We selected <strong>value</strong> from our previous step as the Content for this step.</p>

<p>Within this step we added an additional action for <strong>Execute stored procedure (V2)</strong>.  Here we specified the SQL Stored Procedure that would insert the data into the table and set the appropriate values.</p>

<p>In our example, the WorkItemId was at the top level of our OData output so we could select it outright within dynamic content.</p>

<p>Getting the Iteration Dates was a little different.  Here we used expressions to get data from the nested Iteration data - see below:</p>

<p><img src="/images/ProcessJSONOData.png" alt="Process JSON OData" /></p>

<p>And that’s it.  Manually triggering our Flow we could see the data was now Flow-ing into our On-Premise SQL Server.</p>

<p><em>Caveat: This did take a few evenings to get this up and running, so thought it would be worthwhile documenting our journey just in case anyone else wanted to pull data from Azure DevOps in this way.</em></p>

<p>Now, I appreciate there are probably many ways that this could have been achieved and maybe in fewer steps too.  If so, I’d be happy to hear from you!</p>

<p>Thanks for taking the time to have a read.</p>]]></content><author><name>Kev Ritchie</name></author><category term="azure" /><category term="devops" /><category term="reporting" /><category term="power" /><category term="automate" /><category term="DevOps" /><summary type="html"><![CDATA[So, you’ve got some data in your Azure DevOps project that you’d like to get out and turn into a bespoke report?]]></summary></entry><entry><title type="html">Passed the Azure Developer Associate Exam</title><link href="https://kevinritchie.co.uk/certifications/2020/06/06/passed-the-azure-developer-associate-exam.html" rel="alternate" type="text/html" title="Passed the Azure Developer Associate Exam" /><published>2020-06-06T21:18:52+00:00</published><updated>2020-06-06T21:18:52+00:00</updated><id>https://kevinritchie.co.uk/certifications/2020/06/06/passed-the-azure-developer-associate-exam</id><content type="html" xml:base="https://kevinritchie.co.uk/certifications/2020/06/06/passed-the-azure-developer-associate-exam.html"><![CDATA[<p><strong>I’ve taken many Microsoft exams over the years, but this was the first time sitting one in the comfort of my own home.</strong></p>

<p>Honestly, I was dreading the experience, but I’m not too sure why?</p>

<p>I had the exam booked for my local test centre well before the lock down and I’m sure, like most who had exams booked, received an email to say that I had to reschedule it.</p>

<p>I kept following the test centre’s website for updates to see when they would re-open and subsequently set another date for my exam.  Great!</p>

<p>I had spent months revising for the AZ-203 Azure Developer Associate exam before the new AZ-204 exam came along, so wanted to continue down that path.  Luckily the expiration dates for certain exams (including AZ-203) had been extended.</p>

<p>So why the change to take the exam at home?  Curiosity!  I had seen a number of the #AzureFamily and others in the Twitter-verse taking their exams in the comfort of their own homes and thought why not give it a go.</p>

<p>I was meant to be on holiday the last two weeks with the family, but like everyone else, our plans had to change.  With the next two weeks on annual leave, it was a golden opportunity to catch up on the revision and get the exam under my belt.</p>

<p>After reading through the do’s and don’ts of taking exams at home, I took the plunge.  I cancelled my exam and re-booked it for home for the penultimate day of my annual leave.</p>

<p>There are a few things you need to do to prepare your exam area.  They basically boil down to nothing on your desk, no watch, external monitors disconnected, keeping your mobile phone within arms reach (just in case you need to contact the exam invigilator; equally you can use in the in-exam chat) and most important of all, no noise in the house.  Which is difficult if you live in these new build houses where the walls are paper thin.  Was this my major apprehension?</p>

<p>I’m not going to lie, my office space is small, lots on the desk as I share with my wife and the office also doubles up as my daughter’s play room.  I needed an alternative!</p>

<p>Ah ha! My daughter has a small table in the living room, there’s very little tech in the living room and it’s all spaced out.  Great!  I’ll set up in there.  I made sure to cover the TV just in case too!</p>

<p>On the morning of the exam, I started the check in process 30 minutes early just to make sure I didn’t run into any technical issues.  My advice, definitely try and get in there early.  You’ll need to go through a system check, verify your identity, take pictures of your work area (front, back, left and right).  Once this is done, you’ll be connected to an invigilator who will run through some final check with you to make sure there’s nothing in the area e.g. other tech, people.  They’ll make sure you’re not wearing a watch and ask that you place your phone just out of arms reach.  Once you’ve got past that, you’re good to go.</p>

<p>Honestly I couldn’t tell the difference between the test centre exam experience and the experience of sitting it at home.  The testing platform was very much the same.  The only thing you’ll notice is that you can see yourself being filmed and that you have access to a chat window in case you run into any issues.</p>

<p>If I can take anything from the experience, it’s that I actually preferred taking the exam in the comfort of my own home.</p>

<p>As you can tell, I did successfully pass.</p>

<p>I appreciate that AZ-203 is shortly going to expire in lieu of AZ-204 being released, but should anyone be going down that path and want to know what study material I used, I used Pluralsight.</p>

<p>The course can be found <a href="https://www.pluralsight.com/paths/developing-solutions-for-microsoft-azure-az-203" target="_blank">here</a>.  Be prepared, there’s 59 hours worth of content!  It’s a couple of years out of date so expect some things to look and work a little differently, but nothing major to worry about.</p>]]></content><author><name>Kev Ritchie</name></author><category term="certifications" /><category term="Certification" /><summary type="html"><![CDATA[I’ve taken many Microsoft exams over the years, but this was the first time sitting one in the comfort of my own home.]]></summary></entry><entry><title type="html">What year is it?!</title><link href="https://kevinritchie.co.uk/me/2019/09/11/where-have-i-been.html" rel="alternate" type="text/html" title="What year is it?!" /><published>2019-09-11T21:18:52+00:00</published><updated>2019-09-11T21:18:52+00:00</updated><id>https://kevinritchie.co.uk/me/2019/09/11/where-have-i-been</id><content type="html" xml:base="https://kevinritchie.co.uk/me/2019/09/11/where-have-i-been.html"><![CDATA[<p><strong>So where have you been all this time! 7 years is a long time, right?</strong></p>

<p>So, where have I been all this time?</p>

<p>You probably didn’t notice, but the content on my website stopped some 7 years ago now!</p>

<p>In short, life has been super busy.</p>

<p>I married my gorgeous wife, commissioned into the RAF Volunteer Reserve (Training) Branch within the Air Training Corps; building up a successful Squadron with over 100 Cadets, and welcomed our little monkey; Amber, into the world!</p>

<p>Our daughter brings us joy everyday and certainly keeps us on our toes.  You can keep up with her antics over on her Facebook Page <a href="https://www.facebook.com/UpSideOfDownsUK/" target="_blank">Up Side of Down’s</a>.  I guarantee her content will be more entertaining than mine.</p>]]></content><author><name>Kev Ritchie</name></author><category term="me" /><category term="Me" /><summary type="html"><![CDATA[So where have you been all this time! 7 years is a long time, right?]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kevinritchie.co.uk/images/Logo.jpg" /><media:content medium="image" url="https://kevinritchie.co.uk/images/Logo.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>