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
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:
For further information refer SO-Thread.