Search code examples
c#powershellazure-functionsexchange-online

The module 'Microsoft.PowerShell.Utility' could not be loaded. For more information, run 'Import-Module Microsoft.PowerShell.Utility'


I have a working console application in local that runs exchangeonline using CertificateThumbprint, AppID, & Organization as Credentials. The problem is when converted on function app and run locally, i received an error when importing the exchangeonline module:

[2024-10-07T03:46:44.417Z] Error during Import-Module: The module 'Microsoft.PowerShell.Utility' could not be loaded. For more information, run 'Import-Module Microsoft.PowerShell.Utility'.

I've tried searching on the browser for fix but no solutions most of the queries.

Here is my function for connecting exchange online:

public static Task GetUserMailbox(string appId, string certificatePath, string certificatePassword, string organization, string userPrincipalName, ILogger log)
{
    try
    {
        // Set TLS protocol to 1.2
        System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;

        // Optionally bypass SSL certificate validation (not recommended for production)
        System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

        // Load the certificate from the file using the password
        var certificate = new X509Certificate2(certificatePath, certificatePassword);

        var iss = InitialSessionState.CreateDefault2();
        iss.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Bypass;

        using (Runspace remoteRunspace = RunspaceFactory.CreateRunspace(iss))
        {
            remoteRunspace.Open(); // Ensure the Runspace is opened

            using (PowerShell powershell = PowerShell.Create())
            {
                powershell.Runspace = remoteRunspace;

               

                // Import the Exchange Online module
                powershell.AddCommand("Import-Module")
                    .AddArgument("ExchangeOnlineManagement")
                    .Invoke();

                // Check for errors in Import-Module
                if (powershell.HadErrors)
                {
                    foreach (var error in powershell.Streams.Error)
                    {
                        log.LogError($"Error during Import-Module: {error}");
                    }
                    return Task.CompletedTask; // Stop further execution if the module import fails
                }
                log.LogInformation("Successfully imported ExchangeOnlineManagement module.");

                // Clear the previous command
                powershell.Commands.Clear();

                // Connect to Exchange Online
                powershell.AddCommand("Connect-ExchangeOnline")
                    .AddParameter("AppId", appId)
                    .AddParameter("CertificateThumbprint", certificate.Thumbprint)
                    .AddParameter("Organization", organization)
                    .AddParameter("ShowBanner", false);
                powershell.Invoke();

                // Check for errors in Connect-ExchangeOnline
                if (powershell.HadErrors)
                {
                    foreach (var error in powershell.Streams.Error)
                    {
                        log.LogError($"Error during Connect-ExchangeOnline: {error}");
                    }
                    return Task.CompletedTask; // Stop further execution if connection fails
                }
                log.LogInformation("Successfully connected to Exchange Online.");

                // Get user mailbox details using Get-CASMailbox and Select-Object
                powershell.Commands.Clear();
                powershell.AddCommand("Get-CASMailbox")
                    .AddParameter("Identity", userPrincipalName)
                    .AddCommand("Select-Object")
                    .AddParameter("Property", new string[] { "DisplayName", "UserPrincipalName", "ForwardingSmtpAddress", "OWAEnabled" });
                var mailboxResults = powershell.Invoke();

                // Check for errors in Get-CASMailbox
                if (powershell.HadErrors)
                {
                    foreach (var error in powershell.Streams.Error)
                    {
                        log.LogError($"Error during Get-CASMailbox: {error}");
                    }
                    return Task.CompletedTask; // Stop if mailbox retrieval fails
                }

                // Display results
                if (mailboxResults != null && mailboxResults.Count > 0)
                {
                    foreach (var result in mailboxResults)
                    {
                        log.LogInformation(result.ToString()); // Safely convert PSObject to string
                    }
                }
                else
                {
                    log.LogInformation("No mailbox found or no results returned.");
                }

                // Disconnect from Exchange Online
                powershell.Commands.Clear();
                powershell.AddCommand("Disconnect-ExchangeOnline")
                    .AddParameter("Confirm", false);
                powershell.Invoke();

                // Check for errors in Disconnect-ExchangeOnline
                if (powershell.HadErrors)
                {
                    foreach (var error in powershell.Streams.Error)
                    {
                        log.LogError($"Error during Disconnect-ExchangeOnline: {error}");
                    }
                }
                else
                {
                    log.LogInformation("Successfully disconnected from Exchange Online.");
                }
            }
        }
    }
}

Error Starts on importing module.

EDIT: Added importing powershell utiliy and this error comes:

Exception: Cannot find the built-in module 'Microsoft.PowerShell.Utility' that is compatible with the 'Core' edition. Please make sure the PowerShell built-in modules are available. They usually come with the PowerShell package under the $PSHOME module path, and are required for PowerShell to function properly.

Im using sdk 7.4.5


Solution

  • As an alternative you can use System.Management.Automation module and code:

    Function.cs:

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Azure.Functions.Worker;
    using Microsoft.Extensions.Logging;
    using System.Management.Automation.Runspaces;
    using System.Management.Automation;
    
    namespace FunctionApp232
    {
        public class Function1
        {
            private readonly ILogger<Function1> ri_lg;
    
            public Function1(ILogger<Function1> logger)
            {
                ri_lg = logger;
            }
    
            [Function("Function1")]
            public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req)
            {
                ri_lg.LogInformation("Hello Rithwik Bojja, The function started execution");
                var rith = InitialSessionState.CreateDefault2();
                rith.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Unrestricted;
    
                using var cho_ps = PowerShell.Create(rith);
                var rith_out = cho_ps.AddScript(@"
    
                Install-PackageProvider -Name Nuget -Scope CurrentUser –Force
                Install-Module –Name PowerShellGet -Scope CurrentUser –Force
                Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force
                Install-Module -Name Az.Resources -AllowClobber -Scope CurrentUser
    
                Import-Module 'Az'
                Import-Module 'Az.Accounts'
                try{
                $rith_clnt_id = 828a057
                $rith_clnt_scret = XyG86cVj
                $rith_tnt_Id = 9329c02a-4f6d
                $pwd = ConvertTo-SecureString $rith_clnt_scret -AsPlainText -Force
                $rith = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $rith_clnt_id, $pwd
                Connect-AzAccount -ServicePrincipal -Credential $rith -Tenant $rith_tnt_Id
                New-AzResourceGroup -Name 'rithwik_test_rg' -Location 'West US 2'
                    }
                catch {
                 Write-Host 'Not created'
                 }
    
                ").Invoke();
                Console.WriteLine(rith_out);    
                return new OkObjectResult("Welcome to Azure Functions!");
            }
        }
    }
    

    I have used Service principal, you can use thumbprint or any other way login way.

    csproj:

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <AzureFunctionsVersion>v4</AzureFunctionsVersion>
        <OutputType>Exe</OutputType>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
      </PropertyGroup>
      <ItemGroup>
        <FrameworkReference Include="Microsoft.AspNetCore.App" />
        <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.23.0" />
        <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.1.0" />
        <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.2.1" />
        <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.4" />
        <PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" />
        <PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.2.0" />
        <PackageReference Include="System.Management.Automation" Version="7.4.5" />
      </ItemGroup>
      <ItemGroup>
        <None Update="host.json">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </None>
        <None Update="local.settings.json">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
          <CopyToPublishDirectory>Never</CopyToPublishDirectory>
        </None>
      </ItemGroup>
      <ItemGroup>
        <Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext" />
      </ItemGroup>
    </Project>
    

    Output:

    enter image description here

    enter image description here

    For further information refer SO-Thread.