Monday, October 11, 2021

One reason for Connect-SPOService error: The sign-in name or password does not match one in the Microsoft account system

Today we have faced with interesting issue. When tried to use Connect-SPOService cmdlet on one tenant we got the following error:

The sign-in name or password does not match one in the Microsoft account system

Interesting that on other tenants this cmdlet worked properly. Troubleshooting and searching showed that one of the reason of this error can be enabled MFA (more specifically, when Connect-SPOService is used with -Credentials param). However we double checked that for those accounts for which this error was shown MFA was disabled.

Then we tried to login to O365 site with this account in browser and my attention was attracted by the following message which was shown after successful login:

Microsoft has enabled security defaults to keep your account secure:

 


As it turned out on this tenant AAD Security defaults were enabled which forced MFA for all users. In turn it caused mentioned error with Connect-SPOService. Solution was to disable security defaults in AAD properties:

After that error disappeared and we were able to use SPO cmdlets.

Friday, October 8, 2021

Fix Azure web job startup error "Storage account connection string 'AzureWebJobsStorage' does not exist"

If you develop continuous Azure web job which monitors e.g. Azure storage queue messages then your code may look like this (in our example we will use latest v3 version of Microsoft.Azure.WebJobs):

var builder = new HostBuilder();
static void Main(string[] args)
{
    var builder = new HostBuilder();
    builder.UseEnvironment("development");
    builder.ConfigureWebJobs(b =>
    {
        b.AddAzureStorageCoreServices();
        b.AddAzureStorage();
    });
    builder.ConfigureLogging((context, b) =>
    {
        b.AddConsole();
    });
    var host = builder.Build();
    using (host)
    {
        host.Run();
    }
}

public static void ProcessQueueMessage([QueueTrigger("myqueue")] string message, TextWriter log)
{
    ...
}

Here in Main method (which is entry point for continuous web job) we create host which subscribes on specified queue. When new message will be added to the queue it should trigger our ProcessQueueMessage handler. However on startup you may get the following error:

Storage account connection string 'AzureWebJobsStorage' does not exist

This error is thrown when host can't find connection string AzureWebJobsStorage of Azure storage where specified queue is located. In order to avoid this error we need to create appsettings.json file in our project which looks like this:

{
  "ConnectionStrings": {
    "AzureWebJobsStorage": "..."
  }
}

Also in file's properties we need to set Copy to output directory = Copy always so it will be always copied in output directory with exe file. After that error should disappear and web job should be successfully started.

Friday, October 1, 2021

Use self-signed SSL certificate for web API hosted in Azure App service

Suppose that we develop web API (e.g. https://api.example.com) and want to use self-signed certificate for it's domain name. Since it is web API we usually don't worry a lot about errors or warning which browser may show when you try to access it by url there. At the same time we don't want to compromise security and thus want to allow only our SSL certificate with known hash/thumbprint.

First of all we need to create self-signed certificate for domain name which will be used for web API (it should contain CN=api.example.com otherwise it won't be possible to bind it to custom domain name in Azure App service). Also certificate should be created with exportable private key (pfx) so we can use it in Azure. It can be done by the following PowerShell:

$start = [System.DateTime]::Now.AddDays(-1)
$end = $start.AddYears(2)
New-SelfSignedCertificate `
    -FriendlyName "api.example.com" `
    -KeyFriendlyName "api.example.com" `
    -KeyAlgorithm "RSA" `
    -DnsName "api.example.com" `
    -NotBefore $start `
    -NotAfter $end `
    -KeyUsage CertSign, CRLSign, DataEncipherment, DigitalSignature, NonRepudiation `
    -KeyUsageProperty All `
    -KeyLength 2048 `
    -CertStoreLocation cert:\LocalMachine\My `
    -KeyExportPolicy Exportable
$cert = Get-ChildItem -Path Cert:\LocalMachine\my | where-object { $_.Subject -eq "CN=api.example.com" }
$password = Read-Host -Prompt "Enter Password to protect private key" -AsSecureString
Export-PfxCertificate -Cert $cert -Password $password -FilePath api.example.com.pfx

Here we first create certificate itself and then export it's private key to file system.

Next step is to add custom domain name api.example.com to Azure app service where we will host our API. First of all we need to ensure that current pricing tier supports "Custom domains / SSL" feature. Currently minimal pricing tier with this option is B1 (it is not free):

Then we go to App service > Custom domains > Add custom domain and specify desired domain name for our web API:

Before Azure will allow to use custom domain name we will need to prove hostname ownership by adding TXT and CNAME DNS records for specified domain name - it is done in hosting provider control panel (here are detailed instructions of the whole process: Map an existing custom DNS name to Azure App Service).

Last part is related with client which call our web API. Since web API uses self-signed certificate attempt to call it from C# using HttpClient will fail. We need to configure it to allow usage of our SSL certificate but at the same time don't allow to use other self-signed SSL certificates. It can be done with the following C# code:

// thumprint of self-signed SSL certificate
private const string CERT_THUMBRINT = "...";

ServicePointManager.ServerCertificateValidationCallback = validateServerCertficate;
...
private static bool validateServerCertficate(
    object sender,
    X509Certificate cert,
    X509Chain chain,
    SslPolicyErrors sslPolicyErrors)
{
    if (sslPolicyErrors == SslPolicyErrors.None)
    {
        // Good certificate.
        return true;
    }

    return string.Compare(cert.GetCertHashString(), CERT_THUMBRINT, true) == 0;
}

In this code we instruct ServicePointManager to allow only our self-signed certificate (if error occurred during checking of SSL certificate when connection to remote host is established). Those it won't allow connections to hosts which use other self-signed SSL certificates.

Update 2021-10-15: see also post Convert self-signed SSL certificate's private key from .pfx to .key and .crt which describes how to export private key of self-signed certificate from pfx to key/crt.

Thursday, September 30, 2021

Speaking on online IT community event

Today I was speaking on online IT community event about development and debugging of Azure functions: https://tulaitcommunity.ru. On this event I wanted to show how to efficiently develop Azure functions and use them in Sharepoint Online. Development of Azure functions for Sharepoint Online require skills and experience of usage of many tools like Visual Studio, Postman, ngrok, PnP PowerShell, SPO browser. They were used in my demo. Presentation from speech is available from Slideshare here: https://www.slideshare.net/sadomovalex/azure-sharepoint-online. Recording from online meeting is available on youtube (on Russian language):


 

Hope that it was interesting :)

Wednesday, September 1, 2021

List and delete Azure web jobs using KUDU Web Jobs REST API

Some time ago I wrote how to remove Azure web jobs using AzureRM PowerShell module: How to remove Azure web job via Azure RM PowerShell. However since that time AzureRM was discontinued. MS recommended to switch to new Az module instead. The problem that if you will follow AzureRM migration guide then the same code won't work anymore. The main problem is that new Get-AzResource cmdlet doesn't return resources with type microsoft.web/sites/triggeredwebjobs (although it still returns  other Azure resources like App services, Azure functions, Storage accounts, KeyVaults, SSL certificates). It means that you also can't use Remove-AzResource cmdlet because there won't be resource name which you may pass there. For now I submitted issue to Azure Powershell github about that: Get-AzResource doesn't return web jobs. But let's see how we may workaround that until MS will fix it.

There is another open issue in Azure PowerShell github ARM :: WebApp Web Job Cmdlets which reports about lack of cmdlets for managing web jobs. It was created in 2015 and is still opened. One suggestion there is to use az cli tool. However it is not always possible (e.g. if you run your PowerShell in runbook or can't add this dependency to your script).

Fortunately there is KUDU Web Job REST API which may be utilized for this purpos. In order to list available web jobs we may use the following code:

$token = Get-AzAccessToken
$headers = @{ "Authorization" = "Bearer $($token.Token)" }
$userAgent = "powershell/1.0"
Invoke-RestMethod -Uri "https://{appServiceName}.scm.azurewebsites.net/api/triggeredwebjobs" -Headers $headers -UserAgent $userAgent -Method GET

It will return list of all existing (in our example triggered) web jobs in specific App service. In order to delete web job we need to send HTTP DELETE request to web job's endpoint:

$token = Get-AzAccessToken
Invoke-RestMethod -Uri "https://{appServiceName}.scm.azurewebsites.net/api/triggeredwebjobs/{webjobName}" -Headers $headers -UserAgent $userAgent -Method DELETE

There are other commands as well (like run job, list running history and others). You may check them in REST API documentation using link posted above.

Thursday, August 26, 2021

Camlex.Client 5.0 released: support for .NET 5.0 and .NETStandard 2.0 target frameworks

Good news for Sharepoint developers who use Camlex library: today new version of Camlex.Client 5.0.0 has been released. In this version native support for .NET 5.0 and .NET Standard 2.0 target frameworks was added. .NET Framework 4.5 is also still there for backward compatibility.

With this new version you may use Camlex.Client in your .NET 5.0 or .NET Core 3.1/2.1 in native mode. I.e. there won't be warning anymore that Camlex is referenced using .NET Framework compatibility mode. Each Camlex.Client nuget package now has assemblies for 3 target frameworks:

  • .NET Framework 4.5
  • .NET Standard 2.0
  • .NET 5.0

which should cover most of development needs. The following table shows examples which application will use which Camlex.Client version depending on its target framework:

App's target frameworkCamlex.Client target framework
.NET Framework 4.5.NET Framework 4.5
.NET Framework 4.6.1.NET Framework 4.5
.NET Framework 4.7.2.NET Framework 4.5
.NET Core 2.1.NET Standard 2.0
.NET Core 3.1.NET Standard 2.0
.NET 5.0.NET 5.0

All Camlex.Client nuget packages has been updated with this change:

  • Camlex.Client.dll for SP Online
  • Camlex.Client.2013 for SP2013 
  • Camlex.Client.2016 for SP2013
  • Camlex.Client.2019 for SP2013

Thursday, August 19, 2021

Method not found error in UnifiedGroupsUtility.ListUnifiedGroups() when use PnP.Framework 1.6.0 with newer version of Microsoft.Graph

Today I've faced with interesting problem: in our .Net Framework 4.7.2 project we use PnP.Framework 1.6.0 (latest stable release currently) which was built with Microsoft.Graph 3.33.0. Then I updated Microsoft.Graph to the latest 4.3.0 version and after that UnifiedGroupsUtility.ListUnifiedGroups() method from PnP.Framework started to throw the following error:

Method not found: 'System.Threading.Tasks.Task`1<Microsoft.Graph.IGraphServiceGroupsCollectionPage> Microsoft.Graph.IGraphServiceGroupsCollectionRequest.GetAsync()'.

When I analyzed the error I found the following: Microsoft.Graph 3.33.0 contains IGraphServiceGroupsCollectionPage interface which has the following method:

In Microsoft.Graph 4.3.0 signature of this method has been changed: cancellationToken became optional parameter:

As result we got Method not found error in runtime. Interesting that app.config had binding redirect for Microsoft.Graph assembly but it didn't help in this case:

<dependentAssembly>
  <assemblyIdentity name="Microsoft.Graph" publicKeyToken="31bf3856ad364e35" culture="neutral" />
  <bindingRedirect oldVersion="0.0.0.0-4.3.0.0" newVersion="4.3.0.0" />
</dependentAssembly>

Also interesting that the same code works properly in project which targets .NET Core 3.1.

For .Net Framework project workaround was to copy code of UnifiedGroupsUtility.ListUnifiedGroups() from PnP.Framework to our project (fortunately it is open source) and then call this copied version instead of version from PnP.Framework. When new version of PnP.Framework will be released which will use newer version of Microsoft.Graph we may revert this change back.