Author: James Mounsey-Moran

  • Enhancing Your DevOps Reporting in Power BI

    Enhancing Your DevOps Reporting in Power BI

    My role in Product Management requires me to use DevOps a fair amount, lets just say I always have it open in one way or another. And whilst there are a number of tools out there these days to manage backlogs, roadmaps and everything else in between, it turns out DevOps and PowerBI were all I needed. This blog post focuses on enhancing your DevOps reporting in Power BI.

    The challenge

    The main connector in PowerBI to pull data from DevOps is perfect for getting started but it only pulls a basic set of data.

    The default connector for example if we take the Work Items – All history endpoint gives you the following:

    • Area Path
    • Assigned To
    • Iteration Path
    • Work Item Id
    • Title
    • State
    • Work Item Type
    • Data
    • is Current

    And this is generally fine if you want a quick view. But if you need to review priorities, further information under those user stories. All those useful data points that can make a much more effective dashboard, we need to go further.

    Luckily we can expand this data out using APIs by simply using this first initial connection as a base.

    The function

    In PowerBI if we add the following as a function thats queries the DevOps API directly, meaning we can then use the Work Item Id to get those extra details

    In the below Ive set DEVOPSORG and DEVOPSPROJECT as parameters matching up with my Organisation name and Project Name.

    https://github.com/AetherAdv/powerbi_product_devopsfunction

    let
        FetchWorkItemSummary = (workItemId as text) as table =>
        let
            // Compose the request URL with API version
            EndpointUrl = "https://dev.azure.com/DEVOPSORG/DEVOPSPROJECT/_apis/wit/workitems/" & workItemId & "?api-version=7.1-preview.3",
            
            // Attempt to retrieve JSON data from Azure DevOps
            RawResponse = try Json.Document(VSTS.Contents(EndpointUrl)) otherwise error "No details for ID " & workItemId,
            
            // Verify expected structure
            HasFieldsSection = if Record.HasFields(RawResponse, "fields") then RawResponse[fields] else error "Missing 'fields' data for item " & workItemId,
            
            // Pull out specific fields
            TaskPriority = Record.FieldOrDefault(HasFieldsSection, "Microsoft.VSTS.Common.Priority", null),
            TaskDescription = Record.FieldOrDefault(HasFieldsSection, "System.Description", null),
    
            // Convert to a table format
            Output = Table.FromRecords({
                [Priority = TaskPriority, Description = TaskDescription]
            })
        in
            Output
    in
        FetchWorkItemSummary

    As part of this code Ive then collected the Priority and the Description, so instantly gaining more useful information for reporting.

    There are of course more fields that we can then collect if needed. Use this table below to identify the field reference and drop into the function.

    Field NameField ReferenceNotes
    TitleSystem.TitleShort name of the work item
    StateSystem.StateCurrent workflow state (e.g. New, Active, Closed)
    Work Item TypeSystem.WorkItemTypeType (e.g., Bug, Task, Epic)
    Created BySystem.CreatedByUser who created the work item
    Created DateSystem.CreatedDateWhen the item was created
    Changed BySystem.ChangedByLast user who changed it
    Changed DateSystem.ChangedDateWhen the item was last updated
    Assigned ToSystem.AssignedToCurrent assignee
    TagsSystem.TagsComma-separated string of tags
    Area PathSystem.AreaPathHierarchical area assignment
    Iteration PathSystem.IterationPathSprint or iteration it belongs to
    Effort / Story PointsMicrosoft.VSTS.Scheduling.StoryPointsOften used for estimation
    Remaining WorkMicrosoft.VSTS.Scheduling.RemainingWorkTypically used in Tasks
    PriorityMicrosoft.VSTS.Common.PriorityUsed for sorting/backlog
    SeverityMicrosoft.VSTS.Common.SeverityOften used in bugs
    DescriptionSystem.DescriptionFull rich-text description

    Why?

    This data now forms the basis of dashboards I and other stakeholders rely on when reviewing the backlog, keeping up to date on the road map and much more. Enhancing your DevOps reporting in Power BI means we can get the extra fields we need for stronger reporting capability.

    Coming Next

    Next up I will show some of this data in some of the dashboards I use today, how I’m continuously improving them to support better decision making and how you can also looking to use them in your own environment.

  • Building a game with AI – Microsoft Copilot – Day 1

    Building a game with AI – Microsoft Copilot – Day 1

    Building a game with AI – Microsoft Copilot

    Over the past few months, I’ve been teaching my kids about AI, mainly through making pictures and songs. They’ve been in stitches as they have songs made up about them in the most ridiculous ways and they have started to understand roughly what AI can do.

    But we hadn’t gone as far as building a game, until now!

    We started with Microsoft Copilot, and the first game probably took less than 30 minutes!


    It all started with a simple request to Copilot. I said to the kids “ask Copilot for game ideas that can be built using HTML, CSS, and JavaScript” I wasn’t sure what we would get, but it gave us loads of ideas.

    Copilot didn’t just give us some basic ideas. Instead, it helped us get a framework for a racing game. A simple, yet exciting project that we could expand upon and suddenly it was their game.


    Once we had the foundation, the real fun began. The kids decided what they wanted to change, what they wanted to add, and what features would make the game their own.

    We started with the basics: a car driving around a simple race track. From there, every idea was coming together all with the help of AI. They wanted to customize the car, change the environment, all sorts. They were learning to modify the code in real-time and seeing immediate results, which was awesome to be fair (I did Games Dev at Uni and this part used to take ages!)


    The best part about this whole experience was how they kept tweaking and adding new features to the game. Every time they played, new ideas would pop up. For example, they asked, “Can it rain? Or snow? Or thunder?” So, we started adding weather effects. “Let’s make the car change colour!” Press C to make that happen, of course. And, naturally from my daughter: “I want it to leave rainbows when I drive!” (Press R for that).

    Watching them interact with the game, come up with ideas, and then modify it on their own was absolutely fantastic. It felt like the modern version of those “teach kids to code” programs they do at school, but with the ability to chat with AI, boost their understanding of the code and implement it.

    I uploaded the game to linkedin. I could already see their faces light up at the thought that someone else, maybe thousands of people, could play the game they built. For a 10-year-old, that’s a huge deal!


    The kids are already thinking of ways to improve it, and new features keep coming up. It’s been incredible to see their passion and the way they’ve embraced coding, technology, and AI in such a hands-on way. So I will keep track of how things change, what new things they learn. Questions they ask about AI. AI especially GenAI feels relatively new and exciting for us now, but it will be fully part of their future.

    It’s a reminder of just how powerful AI tools like Copilot can be for creativity and learning. It’s not just about summarising Teams meetings or writing documents it’s about sparking imaginations and helping kids learn through play.

    If you’re thinking of starting a project like this, I highly recommend it. We started with a simple prompt:
    “Give me game ideas that you can build using HTML, CSS, and JavaScript.”

    What happens next is incredible. Watch your kids learn, play, and create something amazing and in no time, they’ll be adding their own ideas to the mix.

    Turns out I am now Product Managing my kids!

    https://www.linkedin.com/posts/james-mounsey-moran-59488b98_ai-microsoftcopilot-gaming-activity-7318201278755442689-szmD?utm_source=share&utm_medium=member_desktop&rcm=ACoAABTGdYABhpaHAbZ6MKcnJPBg7C37ZicFkq8

    What they have added so far, just from prompts to Copilot

    • Car controlled by the arrow keys
    • Racing track following a random path (white lines in the middle of the road)
    • Press C to change car color
    • Car will leave tread marks when turning
    • Car with leave rainbow treads when pressing R
    • Track turns pink when pressing P
    • Weather effects will fade in randomly for a random time (Clear, rain, snow, thunderstorm)

    More coming soon!

  • Updating PowerBI reports with PowerShell

    Updating PowerBI reports with PowerShell

    So this is probably the last PowerShell script for the moment. But this is where it gets interesting, we can now combine some of those scripts to make a quick and easy way of updating PowerBI reports with PowerShell.

    If you have the requirement where you have multiple instances of the same report across the same or different workspaces this script is for you!

    I had that requirement as part of Prism and again as things scaled up, this became a huge time saver. It also removed the potential of human error when moving parameters around, uploading to the correct workspace.

    Similar to the uploading new report script in my previous post, this one will pick up a pbix file then scan over your workspaces (based on when filters you apply). When it finds reports that again match your criteria, it will take a copy of the current parameters, update the pbix file and then load the parameters back in.

    Meaning, you have a PowerBI report uploaded to the service with your latest changes from a single source file, the parameters all remain the same as before, the script then refreshes to pull in the data based on those parameters and all good to go!

    As part of version control, I currently use a parameter to record the current version of my reports. This adds an extra benefit as I can then use this script to check if the reports are already on the expected version. Just in case the script is interrupted, we need to deploy on stages.

    I then add in the new version number as part of the script and this becomes a great way to track report versions when updating PowerBI reports with PowerShell.

    How it works?

    • Connects to Power BI Service using Connect-PowerBIServiceAccount.
    • Retrieves all Power BI workspaces using Get-PowerBIWorkspace.
    • Loops through each workspace and identifies reports whose names start with “AETHER”.
    • For each report:
    • Retrieves the dataset parameters.
    • Updates the first parameter’s value to a specified version if it doesn’t already match.
    • Overwrites the report in the workspace using a specified PBIX file ($FilePath).
    • Takes over the dataset to ensure permissions are set correctly.
    • Updates the dataset parameters.
    • Triggers a dataset refresh.

    Key things you can change

    • $DeployVer: The new deployment version to update the first parameter to (e.g., “2025Q1”).
    • $FilePath: The path to the PBIX file used for updating reports.
    • Parameter 0 is assumed to represent a version parameter (e.g., “2025Q1”)

    https://github.com/AetherAdv/powerbi_powershell_updatereports/upload/main

    # Connect to Power BI Service Account
    Connect-PowerBIServiceAccount
    
    # Set the deployment version to be used for updating parameters
    $DeployVer = "2025Q1"
    
    # Define the path to the PBIX file to be used for report updates
    $FilePath = "C:\Temp\myreport.pbix"
    
    # Define the conflict action for updating reports (e.g., Create or Overwrite existing reports)
    $Conflict = "CreateOrOverwrite"
    
    # Retrieve all Power BI workspaces
    $workspaces = Get-PowerBIWorkspace -All
    
    # Loop through each workspace
    foreach ($workspace in $workspaces) {
    
        # Get all reports in the current workspace with names starting with "AETHER"
        $Reportlist = Get-PowerBIReport -WorkspaceId $workspace.Id | Where-Object { $_.Name -like 'AETHER*' }
    
        # Check if any reports were found in the workspace
        if ($Reportlist) {
            Write-Host "Workspace: $($workspace.Name)" # Log the workspace name
    
            # Loop through each report in the report list
            foreach ($Report in $Reportlist) {
                Write-Host "  Report: $($Report.Name)" # Log the report name
    
                $JsonString = $null # Initialize JSON string variable
    
                # Retrieve the parameters of the dataset associated with the report
                $ParametersJsonString = Invoke-PowerBIRestMethod -Url "https://api.powerbi.com/v1.0/myorg/groups/$($workspace.Id)/datasets/$($Report.DatasetId)/parameters" -Method Get
                $Parameters = (ConvertFrom-Json $ParametersJsonString).value # Convert JSON response to PowerShell object
    
                # Initialize an empty array to hold parameter updates
                $UpdateParameterList = @()
    
                # Loop through each parameter and prepare the update list
                foreach ($Parameter in $Parameters) {
                    $UpdateParameterList += @{ "name" = $Parameter.name; "newValue" = $Parameter.currentValue }
                }
    
                # Check if there are any parameters to update
                if ($UpdateParameterList.Count -gt 0) {
                    # Get the current value of the first parameter
                    $currentparam = $UpdateParameterList[0].newValue
    
                    Write-Host "    Current Parameter 0 Value: $currentparam" # Log the current parameter value
    
                    # Check if the current parameter value matches the deployment version
                    if ($currentparam -ne $DeployVer) {
                        Write-Host "Version does not match. Updating..." # Log the update action
    
                        # Update the first parameter to the new deployment version
                        $UpdateParameterList[0].newValue = $DeployVer
                    }
                    else {
                        Write-Host "Version already matches. Skipping update." # Log if no update is needed
                    }
    
                    # Prepare the JSON payload for updating parameters
                    $JsonBase = @{ "updateDetails" = $UpdateParameterList }
                    $JsonString = $JsonBase | ConvertTo-Json
    
                    # Define the report name
                    $ReportName = $Report.Name
    
                    # Update the existing report in the workspace
                    New-PowerBIReport -Path $FilePath -Name $ReportName -WorkspaceId $workspace.Id -ConflictAction $Conflict
    
                    # Take over the dataset to ensure permissions are set correctly
                    Invoke-PowerBIRestMethod -Url "https://api.powerbi.com/v1.0/myorg/groups/$($workspace.Id)/datasets/$($Report.DatasetId)/Default.TakeOver" -Method Post
    
                    # Update the parameters of the dataset
                    Invoke-PowerBIRestMethod -Url "https://api.powerbi.com/v1.0/myorg/groups/$($workspace.Id)/datasets/$($Report.DatasetId)/Default.UpdateParameters" -Method Post -Body $JsonString
    
                    # Pause for 5 seconds to avoid API rate limits
                    Start-Sleep -Seconds 5
    
                    # Trigger a dataset refresh
                    Invoke-PowerBIRestMethod -Url "https://api.powerbi.com/v1.0/myorg/groups/$($workspace.Id)/datasets/$($Report.DatasetId)/refreshes" -Method Post
                    Write-Host "Refresh started." # Log the refresh action
                }
                else {
                    Write-Host "No parameters found for this dataset." # Log if no parameters are found
                }
            }
        } else {
            Write-Host "No reports found in workspace: $($workspace.Name)" # Log if no reports are found in the workspace
        }
    }
    
    # Log the completion of the script
    Write-Host "Script completed."