CHUVASH.eu

CHunky Universe of Vigourous Astonishing SHarepoint :)

Setting up a HelloWorld Azure Alert

Azure Alerts are awesome for monitoring of solutions in Azure. If you are about to set up your first Alert Rules in Azure, then it’s a guide for you. Configuring alert rules can be quite intimidating at first, with all the options, metrics, evaluation times, etc.

Here is a very very simple setup that can serve as a teaser and help you get started with the Azure Alerts.

I’ll use Teams as an easy way to set up notifications.

The core solution (alert handler) will be an Azure Function, also because it’s fast and easy to set up.

A reliable failing resource

“Reliable failing”, huh? Yes, this oxymoron is the best description of what we are looking for: a resource in Azure that can fail reliably (“fail faster”), so that we can trigger our alerts while developing.

To do that an easy way, we’ll just create a logic app and let it fail all the time.

Run it, and you’ll see how it fails as intended.

The runs history of my failing logic app

When you’re done setting up the alerts, you can remove the failing logic app.

Communication channel

On the other end we need a reliable communication channel.

Let’s pick a channel in a team and create an incoming webhook. I call my webhook alert-hook. (Just to make it easier to follow this guide, it will appear here and there)

Why incoming webhook? Because it is easy to create and send messages to, and also with the right notifications on that channel and the Teams mobile you’ll get the smoothest way of setting up push notifications! Isn’t it great to get your custom alerts directly on you mobile in real time?

Just to make one step at a time, already try the incoming webhook by calling it using PowerShell. Verified small steps make it easier to troubleshoot future potential issues.

$body = "{'text':'hello world'}"
$ct = 'application/json; charset=utf-8'
$uri = "https://outlook.office.com/webhook/6c15f246-4ad8-47a7-ac3c-8c3e4ff96e08@21a772a0-3ad8-483b-bef2-d9c28cfe5dff/IncomingWebhook/7d88ca6b0f7d417bb87d4f8ae8816760/1522ad47-6712-4e3b-b454-d1198e0287a8"
Invoke-RestMethod Method POST ContentType $ct Body $body Uri $uri
view raw call-webhook.ps1 hosted with ❤ by GitHub

When you see the “hello world” from alert-hook in your Teams channel, then you’re ready to proceed with the next step.

Alert Handler

Now it’s time to set up the core of that solution – a handler that will receive alerts and pass it to the Teams channel.

Why do we need an Alert Handler? Well, because you can’t send the alerts directly to a Teams channel (or whatever communication channel you choose), they have different schemas. But also, an Alert Handler is an opportunity to make an alert more readable (e.g. by formatting it as an adaptive card), and even filtering out some alerts or parts of them (e.g. in some scenarios only Fired Events (not Resolved) are relevant for notifications).

For the sake of simplicity, let’s just create PowerShell Azure Function in the Azure Portal. Just choose everything latest (in my case it was PowerShell Core 7.0, Consumption Plan, West Europe). If you uncertain, check this post:

Create a new function alert-hook, and paste the same hello-world code snippet from the above step.

Test and run it once to verify this step. If you see the hello-world again, then it works.

Alert Rule

Now it’s time to set up an Alert Rule, the most intimidating part 🙂 Let’s just get this over with.

Open the Failing Resource, and navigate to its Alerts.

We’ll create an alert rule as simple as possible. For reference you can check this out:

We need those 4 steps. I created a simplified diagram of the properties that you need have in mind:

Alert Rule Scope

When you click on “New alert rule”, the Scope will be already defined, it will point to the “Failing Resource”.

Alert Rule Condition

There are so many signals and possibilities. In this guide, just choose “Runs Failed” as a Signal.

In the Alert Logic, select “Greater than or equal”, 1; 5 minutes Granularity Period and 1 minute Frequency of Evaluation. No comment on them right now. Just do it.

When we’re done, re-visit this page and try other things, right now we just want to have an alert directly when our failing resource fails.

Action Group

Action Group is what gets triggered. It is billed and that’s why it is connected to a Resource Group (it might be another resource group, it does not need to be in the same place as our “Failing Resource”). Just create a new one:

Here is a simplified diagram of an action group:

Action Group Action – Webhook

There are a couple of options for Notifications and Actions to try out. Let’s focus on the Webhook in this guide. In the picture it is called GenericTolleAlertHook.

Copy the Uri from the your function (“Get function url”) and paste it into the Webhook URI.

Important: enable the common alert schema. That will save much of the pain.

Common Alert Schema

The payload in the alert may vary. To make it more predictable for parsing in the alert handler, we just need to enable the common schema, which will be crucial when we will extact and send some data to the Teams channel.

Action Group Bonus Tip: It might be not obvious when you set up it in the Azure Portal, but an alert rule can have 1 or multiple action groups (!). And the other way around: An action group can be used in multiple Alert Rules.

That makes it very flexible, we could create one generic Action Group that notifies Teams and reuse it across alert rules.

Alert Rule Details

The last step is to give the rule a name and description. Keep the Severity as it is right now.

Alert Handler Improvements

We need one more thing to call this guide complete: rather than saying Hello World, we need to have “Alert Fired” and what alert (alert rule name), to make it useful for real.

Let’s re-visit the alert-hook function and make some improvements. Remember the common alert schema? Make sure you enable it in the Alert Rule -> Action Group -> Action. When you do that you will get payloads like these I get:

When you look at them you can see some attributes that we can make use of:

  • $Request.Body.data.essentials
    • .alertRule (name)
    • .monitorCondition (“Fired” vs. “Resolved”)
    • .firedDateTime (vs. resolvedDateTime)
    • .description
    • .severity
    • .alertTargetIDs (https://portal.azure.com/#@/resource + alertTargetIDs) (the logic app)
view raw rule-props.md hosted with ❤ by GitHub

We’ll use the alertRule and monitorCondition properties, that we’ll send in the body of the incoming webhook to Teams:

Let’s test and run. Copy and paste a sample alert payload (with the common alert schema). The links are above.

It should result in a new post in your Teams channel:

Further improvements

A simple alert rule is configured. Enjoy! Discover more and if you would like new challenges here are some tasks that you can try:

Adaptive Card

Try to update the payload in the Teams incoming webhook to make an adaptive card.

Fired vs. Resolved

It might be good to have different paths for Fired and Resolved. I find it confusing when Resolved Notifications appear alongside with Fired Events. It’s better to suppress the Resolved notifications, or at least format them differently, or maybe even post them as answers to an existing posts (the original Fired posts)?

Summary

Azure Alerts are great. Start with a simple set up, see it working and improve continously. An ActionGroup can be reused, you can have a generic Action Group. That makes it easy to set up new alert rules and and you only need to update the action in one place only. Of course, the alert rules can have their specific actions as well, you can connect more than one action group to an alert rule. Use Common Alert Schema to avoid parsing errors and to achieve a generic action group.

Teams is a good notifications destination, especially for your first Alert Rule, it’s easy to set up, does not mean additional costs and (best of all), you and your colleagues can enable notifications the destination channel (channel with your incoming webhook), that way you will be immediately notified when something fails in your Azure Applications, – both on Desktop and in your mobile! Good DevOps, isn’t it?

Listing all renamed sites in SharePoint Online

When you rename a site, a new site is REDIRECTSITE#0, you can get all the sites of that type by running

Get-SPOSite -Template REDIRECTSITE#0

Please consider some caveats with renaming a site url

Automatically detect new sites in SharePoint Online

Original image by William Warby. https://www.flickr.com/photos/wwarby/16414155179/in/photostream/

Sites in SharePoint are created all the time, not only for SharePoint, but also as storage for Yammer, Teams, Planner and other services in Microsoft 365. There are ways to keep track of them, but the ability to automatically detect a new site creation is quite appealing. Automatic detection means a trigger of a Power Automate (Flow) or a Logic App.

There are a few blog posts that exactly describe how you can detect when a new site is created in SharePoint Online:

The provided blog posts are great how-tos, I am not giving you a new how-to for that, I’d like to reason about that solution.

The solution for automatic detection of new sites

Power Automate and Logic Apps can listen to new items in SharePoint. There is a list in the admin site (tenant-admin.sharepoint.com) that has SharePoint Sites as list items, its name is DO_NOT_DELETE_SPLIST_TENANTADMIN_ALL_SITES_AGGREGATED_SITECOLLECTIONS.

That’s it, in essence, it’s just setting up a new flow with “When an item is created in SharePoint” as a trigger, and you have thousands business scenarios you could implement, but let’s dig a little bit deeper.

One List to rule them all

Honestly, I was not aware of that list before I started looking at that. What is that list, why is it called DO_NOT_DELETE_SPLIST_TENANTADMIN_ALL_SITES_AGGREGATED_SITECOLLECTIONS.

The name is hillarious. Why name something to “DO_NOT_DELETE…” and all capslock🤣. But I suppose, there were support cases.

Beware, that list is not documented, that means you’re on your own when Microsoft changes the name or moves the list to somewhere else. So don’t build business critical solutions with that.

From what I can see, that list keeps information about all sites (site collections) in SharePoint Online, even those that are deleted and permanently deleted (?). This might be a source for deeper troubleshooting in some scenarios. It is like an old card index in a library you might have seen long time ago. It is hidden nowadays, but it is still there.

Image by LisaJasminAdams from Pixabay

First, that list is in the SharePoint Admin Site Collection, you need to be at least a SharePoint Administrator to access it. Okay, I’d like to know what’s more in its Site Contents (_layouts/15/viewlsts.aspx):

Well, the UI of that page has not been focused on, but nevermind, the lists are there. But you cannot navigate to that list in the browser directly:

It doesn’t matter since we can use it as a trigger but also the SharePoint REST API to get the items, e.g.:

https://tenant-admin.sharepoint.com/_api/Web/Lists/GetByTitle('DO_NOT_DELETE_SPLIST_TENANTADMIN_AGGREGATED_SITECOLLECTIONS')/Items

You can see more examples of listing the sites in the linked posts. Unfortunately I bumped into an issue when trying to filter the results. If that list contains more than 5000 items (and it will, soon or later), you’ll have to deal with the ListView Threshold.

If you filter on Modified, you won’t able to anything because of the ListView Threshold, but filtering on Created will work.

But this is a side note, this post is automatically detecting new sites, not listing them

Alternative solutions

Using this kind of a hidden list mentioned above is a bit of a hack. I’d say it’s okay as long as it works, and it serves an complementary function, e.g. notifying IT about new sites, and the work is backed up by documented and reliable alternatives:

SharePoint Online Admin

Visiting “Active Sites” in SharePoint Online Admin gives you all the sites, you can sort by Created and see all the new sites. You cannot set up an alert or a flow directly from that, but maybe there will be some built-in functionality for that.

Office 365 Usage Reports

You can get all the sites in an Usage Report, their created, size, last activity etc. It’s not real time, but if you’re fine with 1-2 days delay, you can get this report, extract the new ones and do whatever you wanted to do in your original scenario/need.

SharePoint PowerShell Module

It’s worth mentioning, too, although it’s “heavy”. In a tenant with many sites, the scripts for getting all the sites and connected groups may take hours. I am refering to those scripts that start with Connect-SPOService.

Permissions, Licenses, and Security

The SharePoint connection that listens to the DO_NOT_DELETE_SPLIST_TENANTADMIN_ALL_SITES_AGGREGATED_SITECOLLECTIONS list in the Admin Site Collection needs to be set up with a SharePoint Administrator role account. Beware of who has access to that solution (Power Automate or Logic App), this SPO Admin connection in wrong hands can be disastrous. Especially in Azure, pay attention to who has access to the resource, but also to the resource group and the azure subscription.

The account who sets up a Power Automate needs obviously an appropriate license and also Power Automate activated. In my scenario, I don’t need any premium connectors, but depending on your solution, you might need to license your account appopriately.

Scenarios

In “my” scenario, I want to be notified of all new sites in my business unit within a shared tenant, so that we can contact the site owners, provide guidance and also provision important parts (initial folder structure, some spfx solutions etc).

What is your sceanario?

The code

When I am done developing my proof-of-concept, I’ll try to share more details on the actual implementation. It might be an idea to submit the template to the Microsoft Power Community, but I am not sure it will be accepted, given the fact that it uses undocumented and hidden parts of SharePoint Online that soon or later will be subject to change.

Infographics. Who can associate a site with a hub

I found this nice post and a nice decision flowchart.

Low resolution, visit the post to see more.

I also have drawed a simple chart while explaining for my colleagues, you can see it above. I hope this infographics can be useful to more people. By the way, we use the word “Hubber” (sv. Hubbare) for “People who can associate sites to hubs”

Re-discovering Github

Github has changed a lot. While working mostly in Azure DevOps I haven’t followed all the development on Github. Now when I look at that, I am really amazed.

Private Repos for Free accounts

Well, for me it is not as interesting, because with my free account, I don’t see any harm having my labs public. But I know, some people used bitbucket for their smaller private repos.

Github Project

I suppose it is the Azure DevOps Project concept that was copied to Github, a place for planning and having multiple connected repos.

Github CLI

For me the Github CLI is the best news. Being able, from command line, not only to git stuff, but also see and create issues, manage pull requests, repos, releases. That means more automation. I like it.

Also being able to work with gists is nice.

main instead of master

That’s brand new. The word “master” is offensive to some people. (sources: Github, statement, zdnet).

So my test repo is one of the first ones that gets “main” as its main branch. Well, that’s not wrong at all. It connects it back to the olden days of TFS, too 🙂

Is an M365 Group a Yammer Community

Nowadays a Yammer Community gets a corresponding Microsoft 365 Group (Office 365 Group, Unified Group). In your work as an SPO Admin, you might need to differentiate “ordinary” Modern Team Sites from those ones that were created for a Yammer Community.

They both have GROUP#0 as Template. On the actual SPO Site object, there is nothing that you can use to differentiate those. Neither you can use the Office 365 Group information. But there is a way: if you connect to Exchange Online and get the group from there, then there is something useful.

I’ll share a piece of code with you, as the rest of the posts and code snippets, it is “evergreen”, it changes all the time, maybe when you read this in future, there is a better way, but today I am using this code:

# Prerequisites
# AllowBasic as Admin, perhaps in a separate window
# Set-ItemProperty -path 'HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\WinRM\\Client' -Name AllowBasic -Value 1
# Connect to Exchange Online
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline
# you can get $groupId from the SPO object
$exogroup = Get-UnifiedGroup Identity $groupId
$isYammer = $exogroup.GroupSKU -eq "Yammer"
#Bonus: determine if Team is connected (if $isYammer is $false)
$hasTeam = "Team" -in $exogroup.ResourceProvisioningOptions

Estimated Completion in Write-Progress in PowerShell

Have you also got many sites in your tenant? Write-Progress is the bare minimum in a script that goes through all sites. But there is also another nice way to make easier to see the progress – estimated completion time.

Although the idea comes from another blog post (My life is a message), I thought it could be worth sharing it again, especially in the cloud context.

Here is a bit simplified scenario: Getting information for every site. The status message in Write-Progress contains also the estimated completion time.

# This is just an example for time estimations in write-progress,
# though a simplified scenario
$sitesBareMinimum = Get-SPOSite Limit All
$starttime = Get-Date
$count = 0 # kind of an index, counter
$total = $sitesBareMinimum.Count
$sites = $sitesBareMinimum | ForEach-Object {
$site = $_
$estimation = ""
$now = Get-Date
if ($count -gt 0) { # noone wants a DividedByZeroException 🙂
$elapsed = $now $starttime # how much time has been spent
$average = $elapsed.TotalSeconds / $count # how many seconds per site
$totalSecondsToGo = ($total $count) * $average # seconds left
$span = New-TimeSpan Seconds $totalSecondsToGo # time left
$estimatedCompletion = $now + $span # when it will be complete
$estimation = $estimatedCompletion.ToString() # readable estimation
}
$count++
$percent = 100 * $count / $total # percentage complete
$status = "#{0:d5} of $total. Est:d $estimation. $($site.URL)" -f $count # aggregated status message
Write-Progress Activity "Getting information for " Status $status PercentComplete $percent
$siteWithMoreInfo = Get-SPOSite Identity $site.URL # the actual time consuming operation
$siteWithMoreInfo # return the site with more information
}

I included the comments, and it should be straight forward to follow the logic in the script. Every iteration tries to estimate time, by calculating the average time of time per site, mulplying it by the remainder of the sites and adding it to the current time. The more sites are processed, the more accurate is the estimation.

Optimizing lookups in PowerShell

Have you had a PowerShell script that contains two bigger arrays and you wanted merge the information. It can become quite slow if you need to search for every item from array A through all items in array B. The solution is called a HashTable! It might be not an advanced tip for some, but I was really glad to see a huge improvement, so I decided to share it as a post.

My Array A ($sites) is a list of SharePoint Sites (over 10K of them). For every site I need to get information on the owner (such as UsageLocation). In order to minimize calls to the server I want to reuse the information – in my array B: $users. This array of users has also thousands of entries.

Here is my main (simplified) setup:

$users = # @() array, code ommitted for brevity
$sites = # @() array, code ommitted for brevity
$sitesAndOwners = $sites | ForEach-Object {
[PSCustomObject]@{
Site = $_
Owner = GetUserInfo($_.Owner)
}
}

Traversing the array B for the right item for every entry in array A is slow: Where-Object:

function GetUserInfoSlow($upn) {
$user = $users | Where-Object { $_.UserPrincipalName -eq $upn }
if ($user.Count -eq 0) {
$user = Get-AzureADUser SearchString $upn
$users = $users + $user
}
return $user
}

Using a hashtable is much faster:

$sersHash = @{}
function GetUserInfoFast($upn) {
# we check if there is an entry even if value is null
if ($sersHash.Contains($upn)) {
$user = $sersHash[$upn]
}
else {
$user = Get-AzureADUser SearchString $upn
$sersHash.Add($upn, $user)
}
$user
}

In my example it took hours first. Now it takes seconds. A bonus: here is how you can convert an array to a hash table:

#bonus: convert array to a hash table
$users | ForEach-Object {
$usersHash.Add($_.UserPrincipalName, $_)
}

That’s all I’ve got today. Sharing is caring… And of course, big thanks to my colleague Anton H. for his advise.

Page Diagnostics for SharePoint

While trying to set up a new Home Site, I discovered that there is a tool (browser extension) called Page Diagnostics for SharePoint.

After running this, I tried that command again and it was smart enough to detect the problem the tool discovered.

Also Network Trace is available.

Network trace

Page Diagnostics Tool is defnitely a tool to have in the troubleshooting toolbelt for SharePoint.

Setting up a Home Site

Here is the script:

# Sets up a SharePoint Home Site at Skanska
$tenant = "takana17"
Connect-SPOService https://$tenant-admin.sharepoint.com
$baseUrl = "https://$tenant.sharepoint.com"
# site swap takes 1-2 minutes. be patient
# https://docs.microsoft.com/en-us/powershell/module/sharepoint-online/invoke-spositeswap?view=sharepoint-ps
Invoke-SPOSiteSwap SourceUrl "$baseUrl/sites/futurehomesite" TargetUrl "$baseUrl" ArchiveUrl "$baseUrl/sites/oldroot-deleteit"
# Home Site. Docs: https://aka.ms/homesites, it make take some time
Set-SPOHomeSite HomeSiteUrl $baseUrl
view raw spo-home-site.ps1 hosted with ❤ by GitHub

Deploying SPFx using Office 365 cli, custom AAD App and Azure Pipelines

In this post I would like to share some findings from setting a deployment of SPFx. In my work:

  • I need to deploy SPFx solutions using Azure Pipelines
  • I need to use the least privileges/permissions
  • I cannot use Legacy Authentication

First of all, big thanks to @waldekm and the whole community of @office365cli and @m365pnp for the quick help, and that outside working hours.

Let’s take a look at the setup piece by piece

Least Privileges

I followed this guide to set up a custom App Registration for Office 365 CLI in order to use the least privileges:

Custom Azure AD App

For uploading and deploying SPFx packages I found these permissions to be the bare minimum:

  • Delegated Microsoft Graph User.Read
  • Delegated SharePoint AllSites.FullControl

Service Account

The second part is the service account that just has access to one site collection – Tenant App Catalog. That plus Delegated AllSites.FullControl of the app registration narrows the access to just that site. To install apps the Uploader Account needs to be Site Collection Administrator.

Least privileges for SPFx Upload & Deploy

Azure Pipelines

In our project we use Azure Pipelines where we also define the release using .yml. The deployment consists of series of bash inline scripts.

I am not going to describe all the steps for setting up node, npm and installing the office 365 cli. If you already have used Office 365 CLI with the default AAD APP it might look like this:

task: Bash@3 # login
displayName: "Login to O365 spAppCatalogSiteUrl with user $(username)"
inputs:
targetType: "inline"
script: 'o365 login "${{ parameters.spAppCatalogSiteUrl }}" -t password -u $(username) -p $(password)'
task: Bash@3 #upload
displayName: "Upload web part ${{ parameters.spfxPackageName }} to catalog"
inputs:
targetType: "inline"
script: 'o365 spo app add -p "$(Pipeline.Workspace)/${{ parameters.environment }}/${{ parameters.spfxPackageName }}" –overwrite'
task: Bash@3 #deploy
displayName: "Deploy ${{ parameters.spfxPackageName }} web part"
inputs:
targetType: "inline"
script: 'o365 spo app deploy –name "${{ parameters.spfxPackageName }}" –appCatalogUrl "${{ parameters.spAppCatalogSiteUrl }}"'
view raw deploy-spfx.yml hosted with ❤ by GitHub

Now comes the tricky part! If you followed the guide mentioned above, you must have noticed the two environment variables that you need to have:

export OFFICE365CLI_AADAPPID=506af689-32aa-46c8-afb5-972ebf9d218a
export OFFICE365CLI_TENANT=e8954f17-a373-4b61-b54d-45c038fe3188
view raw deploy-spfx-env.sh hosted with ❤ by GitHub

That’s straight forward when you run the cli in your own console. But the fact is (or at least from what I can see), you cannot “export” variables to other pipeline tasks.

Instead of setting the variables in the inline script, we can take advantage of the Bash task parameter called env:.

Some other findings:

  • Office 365 CLI needs them in all three commands: login, spo app add, and spo app deploy
  • If you create and export a variable in a pipeline task, it won’t persist, because every task starts a new shell session.

That means that we need to provide environment variables in every task in the pipeline, that uses Office 365 CLI with a custom Azure AD App. Or is there a better way? Anyway, the version below (the same tasks plus `env`) will work:

task: Bash@3 # login
displayName: "Login to O365 spAppCatalogSiteUrl with user $(username)"
inputs:
targetType: "inline"
script: 'o365 login "${{ parameters.spAppCatalogSiteUrl }}" -t password -u $(username) -p $(password)'
env:
OFFICE365CLI_AADAPPID: "${{ parameters.o365cliAppId }}"
OFFICE365CLI_TENANT: "${{ parameters.tenantId }}"
task: Bash@3 #upload
displayName: "Upload web part ${{ parameters.spfxPackageName }} to catalog"
inputs:
targetType: "inline"
script: 'o365 spo app add -p "$(Pipeline.Workspace)/${{ parameters.environment }}/${{ parameters.spfxPackageName }}" –overwrite'
env:
OFFICE365CLI_AADAPPID: "${{ parameters.o365cliAppId }}"
OFFICE365CLI_TENANT: "${{ parameters.tenantId }}"
task: Bash@3 #deploy
displayName: "Deploy ${{ parameters.spfxPackageName }} web part"
inputs:
targetType: "inline"
script: 'o365 spo app deploy –name "${{ parameters.spfxPackageName }}" –appCatalogUrl "${{ parameters.spAppCatalogSiteUrl }}"'
env:
OFFICE365CLI_AADAPPID: "${{ parameters.o365cliAppId }}"
OFFICE365CLI_TENANT: "${{ parameters.tenantId }}"
view raw deploy-spfx-env.yml hosted with ❤ by GitHub

Eliminating Legacy Authentication

My goal is to remove the need of legacy authentication. Previously we installed spfx packages using PnP PowerShell. PnP PowerShell in Pipelines causes Legacy Authentication, it can be solved, though:

Using Office 365 CLI rather than PnP PowerShell with a certificate has some significant benefits:

  • Office 365 CLI is multi-platform, you can reuse the scripts. PnP PowerShell requires Windows (yet, but still).
  • Setting up certificates and using it in the deployment process is a bigger initial task.

Release Pipelines

Just for completeness, in a classic release pipeline, you can use a bash script to upload and deploy an app:

#runs in Ubuntu 20.04 Bash Task
sudo npm install -g @pnp/office365-cli
export OFFICE365CLI_AADAPPID="$(OFFICE365CLI_AADAPPID)"
export OFFICE365CLI_TENANT="$(OFFICE365CLI_TENANT)"
o365 login –authType password –userName $(AppCatalogUsername) –password "$(AppCatalogPassword)"
export filePath="$(System.DefaultWorkingDirectory)/dist/$(env)/$(fileName)"
o365 spo app add -p "$filePath" –overwrite
o365 spo app deploy –name "$(fileName)" –appCatalogUrl "$(AppCatalogSiteUrl)"
view raw release-bash.sh hosted with ❤ by GitHub

In our example we also send data to Azure CDN using Azure CLI:

az storage blob upload-batch \
–source $(sourceFolder)/bundledFiles \
–destination $(storageContainer)/$(toolPath) \
–account-name $(storageAccount)
view raw azure-cli.sh hosted with ❤ by GitHub
Вула Чăвашла

VulaCV - Чăвашла вулаттаракан сайт

Discovering SharePoint

And going crazy doing it

Bram de Jager - Architect, Speaker, Author

Microsoft 365, SharePoint and Azure

SharePoint Dragons

Nikander & Margriet on SharePoint

Mai Omar Desouki

PFE @ Microsoft

Cameron Dwyer

Office 365, SharePoint, Azure, OnePlace Solutions & Life's Other Little Wonders

paul.tavares

Me and My doings!

Share SharePoint Points !

By Mohit Vashishtha

Jimmy Janlén "Den Scrummande Konsulten"

Erfarenheter, synpunkter och raljerande om Scrum från Jimmy Janlén

Aryan Nava

DevOps, Cloud and Blockchain Consultant

SPJoel

SharePoint for everyone

SharePointRyan

Ryan Dennis is a SharePoint Solution Architect with a passion for SharePoint and PowerShell

SharePoint 2020

The Vision for a Future of Clarity

Aharoni in Unicode

Treacle tarts for great justice

... And All That JS

JavaScript, Web Apps and SharePoint

blksthl

Mostly what I know and share about...

SharePointDiver

SharePoint på ren svenska