.netpowershellazure-functionsmicrosoft-graph-apiexchange-online

ERROR: The specified module 'ExchangeOnlineManagement' was not loaded because no valid module file was found in any module directory


I have created an Azure function under consumption plan, that uses PowerShell, then inside the run.ps1, i added this code, to remove a user from all the groups assigned to the user:-

and the following inside the requirments.psd1 to load the external libraries:-

@{
    'ExchangeOnlineManagement' = '3.*'
    'Microsoft.Graph' = '2.*'
}

as follow:-

enter image description here

but when i run the script, i got those errors:-

        2025-05-08T22:03:07Z   [Error]   ERROR: The specified module 'ExchangeOnlineManagement' was not loaded because no valid module file was found in any module directory.
        
        Exception             : 
            Type    : System.IO.FileNotFoundException
            Message : The specified module 'ExchangeOnlineManagement' was not loaded because no valid module file was found in any module directory.
            HResult : -2147024894
        TargetObject          : ExchangeOnlineManagement
        CategoryInfo          : ResourceUnavailable: (ExchangeOnlineManagement:String) [Import-Module], FileNotFoundException
        FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand
        InvocationInfo        : 
            MyCommand        : Import-Module
        ScriptStackTrace      : at <ScriptBlock>, C:\home\site\wwwroot\TimerTrigger1\run.ps1: line 2
        PipelineIterationInfo : 
        2025-05-08T22:03:07Z   [Error]   ERROR: The specified module 'Microsoft.Graph' was not loaded because no valid module file was found in any module directory.
        
        Exception             : 
            Type    : System.IO.FileNotFoundException
            Message : The specified module 'Microsoft.Graph' was not loaded because no valid module file was found in any module directory.
            HResult : -2147024894
        TargetObject          : Microsoft.Graph
        CategoryInfo          : ResourceUnavailable: (Microsoft.Graph:String) [Import-Module], FileNotFoundException
        FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand
        InvocationInfo        : 
        
        ScriptStackTrace      : at <ScriptBlock>, C:\home\site\wwwroot\TimerTrigger1\run.ps1: line 3
        PipelineIterationInfo : 
        2025-05-08T22:03:07Z   [Error]   ERROR: The term 'Connect-MgGraph' is not recognized as a name of a cmdlet, function, script file, or executable program.
        Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
System.Management.Automation.CommandNotFoundException
System.Management.Automation.ParentContainsErrorRecordException
                    Message : The term 'Connect-MgGraph' is not recognized as a name of a cmdlet, function, script file, or executable program.
                              Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
                    HResult : -2146233087
                TargetObject          : Connect-MgGraph
                CategoryInfo          : ObjectNotFound: (Connect-MgGraph:String) [], ParentContainsErrorRecordException
                FullyQualifiedErrorId : CommandNotFoundException
                InvocationInfo        : 
                ScriptStackTrace      : at <ScriptBlock>, C:\home\site\wwwroot\TimerTrigger1\run.ps1: line 6
            CommandName : Connect-MgGraph
            TargetSite  : 
                Name          : LookupCommandInfo
                DeclaringType : [System.Management.Automation.CommandDiscovery]
                MemberType    : Method
                Module        : System.Management.Automation.dll
            Message     : The term 'Connect-MgGraph' is not recognized as a name of a cmdlet, function, script file, or executable program.
                          Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
            Data        : System.Collections.ListDictionaryInternal
            Source      : System.Management.Automation
            HResult     : -2146233087
        TargetObject          : Connect-MgGraph
        CategoryInfo          : ObjectNotFound: (Connect-MgGraph:String) [], CommandNotFoundException
        FullyQualifiedErrorId : CommandNotFoundException
        InvocationInfo        : 
        ScriptStackTrace      : at <ScriptBlock>, C:\home\site\wwwroot\TimerTrigger1\run.ps1: line 6
        
        2025-05-08T22:03:07Z   [Error]   ERROR: The term 'Connect-ExchangeOnline' is not recognized as a name of a cmdlet, function, script file, or executable program.
        Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
        
        Exception             : 
            Type        : System.Management.Automation.CommandNotFoundException
            ErrorRecord : 
                Exception             : 
                    Type    : System.Management.Automation.ParentContainsErrorRecordException
                    Message : The term 'Connect-ExchangeOnline' is not recognized as a name of a cmdlet, function, script file, or executable program.
                              Check the spelling of the name, or if a path was included, verify that the path is correct and try again.C:\home\site\wwwroot\TimerTrigger1\run.ps1: line 9
            CommandName : Connect-ExchangeOnline
            TargetSite  : 
                Name          : LookupCommandInfo
                DeclaringType : [System.Management.Automation.CommandDiscovery]
                MemberType    : Method
                Module        : System.Management.Automation.dll
            Message     : The term 'Connect-ExchangeOnline' is not recognized as a name of a cmdlet, function, script file, or executable program.
                          Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
            Data        : System.Collections.ListDictionaryInternal
            Source      : System.Management.Automation
            HResult     : -2146233087
        TargetObject          : Connect-ExchangeOnline
        CategoryInfo          : ObjectNotFound: (Connect-ExchangeOnline:String) [], CommandNotFoundException
        FullyQualifiedErrorId : CommandNotFoundExceptionC:\home\site\wwwroot\TimerTrigger1\run.ps1
            Line             : Connect-ExchangeOnline -Identity
                               
            Statement        : Connect-ExchangeOnline
            PositionMessage  : At C:\home\site\wwwroot\TimerTrigger1\run.ps1:9 char:1
                               + Connect-ExchangeOnline -Identity
                               + ~~~~~~~~~~~~~~~~~~~~~~
            PSScriptRoot     : C:\home\site\wwwroot\TimerTrigger1
            PSCommandPath    : C:\home\site\wwwroot\TimerTrigger1\run.ps1
            InvocationName   : Connect-ExchangeOnline
            CommandOrigin    : Internal
        ScriptStackTrace      : at <ScriptBlock>, C:\home\site\wwwroot\TimerTrigger1\run.ps1: line 9
        
        2025-05-08T22:03:07Z   [Error]   ERROR: The term 'Get-MgUser' is not recognized as a name of a cmdlet, function, script file, or executable program.
        Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
        
        Exception             : 
            Type        : System.Management.Automation.CommandNotFoundException
            ErrorRecord : 
                Exception             : 
                    Type    : System.Management.Automation.ParentContainsErrorRecordException
                    Message : The term 'Get-MgUser' is not recognized as a name of a cmdlet, function, script file, or executable program.
            Message     : The term 'Get-MgUser' is not recognized as a name of a cmdlet, function, script file, or executable program.
                          Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
            Data        : System.Collections.ListDictionaryInternal
            Source      : System.Management.Automation
            HResult     : -2146233087
        TargetObject          : Get-MgUser
        CategoryInfo          : ObjectNotFound: (Get-MgUser:String) [], CommandNotFoundException
        FullyQualifiedErrorId : CommandNotFoundExceptionC:\home\site\wwwroot\TimerTrigger1\run.ps1
            Line             : $userList = Get-MgUser -Filter "mail eq '$userEmail' or userPrincipalName eq '$userEmail'"  -ConsistencyLevel eventual -All -Property Id, DisplayName, UserPrincipalName, Mail
                               
            Statement        : Get-MgUser
            PositionMessage  : At 
        2025-05-08T22:03:07Z   [Error]   ERROR: The term 'Get-DistributionGroup' is not recognized as a name of a cmdlet, function, script file, or executable program.
                    HResult : -2146233087
                TargetObject          : Get-DistributionGroup
                CategoryInfo          : ObjectNotFound: (Get-DistributionGroup:String) [], ParentContainsErrorRecordException
                FullyQualifiedErrorId : CommandNotFoundException
    C:\home\site\wwwroot\TimerTrigger1\run.ps1
                    Line             : $dlGroups = Get-DistributionGroup -ResultSize Unlimited
                                       
                    Statement        : Get-DistributionGroup
                    PositionMessage  : At C:\home\site\wwwroot\TimerTrigger1\run.ps1:73 char:13
                                       + $dlGroups = Get-DistributionGroup -ResultSize Unlimited
                                       +             ~~~~~~~~~~~~~~~~~~~~~
                    PSScriptRoot     : C:\home\site\wwwroot\TimerTrigger1
                    PSCommandPath    : C:\home\site\wwwroot\TimerTrigger1\run.ps1
                    InvocationName   : Get-DistributionGroup
                    CommandOrigin    : Internal
                ScriptStackTrace      : at <ScriptBlock>, C:\home\site\wwwroot\TimerTrigger1\run.ps1: line 73
            CommandName : Get-DistributionGroup
            TargetSite  : 
                Name          : LookupCommandInfo
                DeclaringType : [System.Management.Automation.CommandDiscovery]
                MemberType    : Method
                Module        : System.Management.Automation.dll
            Message     : The term 'Get-DistributionGroup' is not recognized as a name of a cmdlet, function, script file, or executable program.
                          Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
            Data        : System.Collections.ListDictionaryInternal
            Source      : System.Management.Automation
            HResult     : -2146233087
        TargetObject          : Get-DistributionGroup
        CategoryInfo          : ObjectNotFound: (Get-DistributionGroup:String) [], CommandNotFoundException
        FullyQualifiedErrorId : CommandNotFoundException
    C:\home\site\wwwroot\TimerTrigger1\run.ps1
            Line             : $dlGroups = Get-DistributionGroup -ResultSize Unlimited
                               
            Statement        : Get-DistributionGroup
            PositionMessage  : At C:\home\site\wwwroot\TimerTrigger1\run.ps1:73 char:13
                               + $dlGroups = Get-DistributionGroup -ResultSize Unlimited

        2025-05-08T22:03:08Z   [Error]   ERROR: The specified module 'ExchangeOnlineManagement' was not loaded because no valid module file was found in any module directory.
        
        Exception             : 
            Type    : System.IO.FileNotFoundException
            Message : The specified module 'ExchangeOnlineManagement' was not loaded because no valid module file was found in any module directory.
            HResult : -2147024894
        TargetObject          : ExchangeOnlineManagement
        CategoryInfo          : ResourceUnavailable: (ExchangeOnlineManagement:String) [Import-Module], FileNotFoundException
        FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand
        InvocationInfo        : 
        ScriptStackTrace      : at <ScriptBlock>, C:\home\site\wwwroot\TimerTrigger1\run.ps1: line 2
        PipelineIterationInfo : 
    
        2025-05-08T22:03:08Z   [Error]   ERROR: The specified module 'Microsoft.Graph' was not loaded because no valid module file was found in any module directory.
        
        Exception             : 
            Type    : System.IO.FileNotFoundException
            Message : The specified module 'Microsoft.Graph' was not loaded because no valid module file was found in any module directory.
            HResult : -2147024894
        TargetObject          : Microsoft.Graph
        CategoryInfo          : ResourceUnavailable: (Microsoft.Graph:String) [Import-Module], FileNotFoundException
        FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand
        InvocationInfo        : 
        ScriptStackTrace      : at <ScriptBlock>, C:\home\site\wwwroot\TimerTrigger1\run.ps1: line 3
        PipelineIterationInfo : 
    
        2025-05-08T22:03:08Z   [Error]   ERROR: The term 'Connect-MgGraph' is not recognized as a name of a cmdlet, function, script file, or executable program.
                    HResult : -2146233087
                TargetObject          : Connect-MgGraph
                CategoryInfo          : ObjectNotFound: (Connect-MgGraph:String) [], ParentContainsErrorRecordException
                FullyQualifiedErrorId : CommandNotFoundException
                ScriptStackTrace      : at <ScriptBlock>, C:\home\site\wwwroot\TimerTrigger1\run.ps1: line 6
            CommandName : Connect-MgGraph
            TargetSite  : 
                Name          : LookupCommandInfo
                DeclaringType : [System.Management.Automation.CommandDiscovery]
                MemberType    : Method
                Module        : System.Management.Automation.dll
            Message     : The term 'Connect-MgGraph' is not recognized as a name of a cmdlet, function, script file, or executable program.

        
        2025-05-08T22:03:09Z   [Error]   ERROR: The term 'Connect-ExchangeOnline' is not recognized as a name of a cmdlet, function, script file, or executable program.
                    HResult : -2146233087
                TargetObject          : Connect-ExchangeOnline
                CategoryInfo          : ObjectNotFound: (Connect-ExchangeOnline:String) [], ParentContainsErrorRecordException
                FullyQualifiedErrorId : CommandNotFoundException
                ScriptStackTrace      : at <ScriptBlock>, C:\home\site\wwwroot\TimerTrigger1\run.ps1: line 9
            CommandName : Connect-ExchangeOnline
            TargetSite  : 
                Name          : LookupCommandInfo
                DeclaringType : [System.Management.Automation.CommandDiscovery]
                MemberType    : Method
                Module        : System.Management.Automation.dll
            Message     : The term 'Connect-ExchangeOnline' is not recognized as a name of a cmdlet, function, script file, or executable program.
                          Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
            Data        : System.Collections.ListDictionaryInternal
            Source      : System.Management.Automation
            StackTrace  : 
        TargetObject          : Connect-ExchangeOnline
        CategoryInfo          : ObjectNotFound: (Connect-ExchangeOnline:String) [], CommandNotFoundException
        FullyQualifiedErrorId : CommandNotFoundException
        InvocationInfo        : 
        ScriptStackTrace      : at <ScriptBlock>, C:\home\site\wwwroot\TimerTrigger1\run.ps1: line 9
        
        2025-05-08T22:03:09Z   [Error]   ERROR: The term 'Get-MgUser' is not recognized as a name of a cmdlet, function, script file, or executable program.
                    HResult : -2146233087
                    InvocationName   : Get-MgUser
                    CommandOrigin    : Internal
                ScriptStackTrace      : at <ScriptBlock>, C:\home\site\wwwroot\TimerTrigger1\run.ps1: line 15
            CommandName : Get-MgUser
            TargetSite  : 
                Name          : LookupCommandInfo
                DeclaringType : [System.Management.Automation.CommandDiscovery]
                MemberType    : Method
                Module        : System.Management.Automation.dll
            Message     : The term 'Get-MgUser' is not recognized as a name of a cmdlet, function, script file, or executable program.
        TargetObject          : Get-MgUser
        CategoryInfo          : ObjectNotFound: (Get-MgUser:String) [], CommandNotFoundException
        FullyQualifiedErrorId : CommandNotFoundException
        ScriptStackTrace      : at <ScriptBlock>, C:\home\site\wwwroot\TimerTrigger1\run.ps1: line 15
        
        2025-05-08T22:03:09Z   [Error]   ERROR: The term 'Get-DistributionGroup' is not recognized as a name of a cmdlet, function, script file, or executable program.
        Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
    System.Management.Automation.CommandNotFoundExceptionSystem.Management.Automation.ParentContainsErrorRecordException
                    Message : The term 'Get-DistributionGroup' is not recognized as a name of a cmdlet, function, script file, or executable program.
                              Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
                    HResult : -2146233087
                TargetObject          : Get-DistributionGroup
                CategoryInfo          : ObjectNotFound: (Get-DistributionGroup:String) [], ParentContainsErrorRecordException
                FullyQualifiedErrorId : CommandNotFoundException
                InvocationInfo        : 
        
                ScriptStackTrace      : at <ScriptBlock>, C:\home\site\wwwroot\TimerTrigger1\run.ps1: line 73
            CommandName : Get-DistributionGroup
                Module        : System.Management.Automation.dll
            Message     : The term 'Get-DistributionGroup' is not recognized as a name of a cmdlet, function, script file, or executable program.

EDIT

I did those changes:-

I am already defining those 2 lines:-

Import-Module ExchangeOnlineManagement
Import-Module Microsoft.Graph

to explicitly import the modules i need.

Anyone is able to advice further?

EDIT-2

Here is run.ps1:-

# Input bindings are passed in via param block.
param($Timer)
#Import-Module ExchangeOnlineManagement
#Import-Module Microsoft.Graph
# Get the current universal time in the default string format.
$currentUTCtime = (Get-Date).ToUniversalTime()

# The 'IsPastDue' property is 'true' when the current function invocation is later than scheduled.
if ($Timer.IsPastDue) {
    Write-Host "PowerShell timer is running late!"
}

# Write an information log with the current time.
Write-Host "PowerShell timer trigger function ran! TIME: $currentUTCtime"
# Connect to Microsoft Graph with required delegated scopes
 Write-Host "Start"
Connect-MgGraph -Identity
 Write-Host "Connect to Graph"



# Target user email
$userEmail = "test@***i.onmicrosoft.com" 

# --- PART 1: Azure AD + Office 365 Groups ---
$userList = Get-MgUser -Filter "mail eq '$userEmail' or userPrincipalName eq '$userEmail'"  -ConsistencyLevel eventual -All -Property Id, DisplayName, UserPrincipalName, Mail

$userObj = $userList | Select-Object -First 1

if (-not $userObj) {
    Write-Host "User $userEmail not found in Microsoft Graph. Skipping AAD removal."
} elseif (-not $userObj.Id) {
    Write-Host "User objectId missing even after lookup. Skipping AAD removal."
} else {
    Write-Host "Found user: $($userObj.DisplayName) ($($userObj.UserPrincipalName))"

    $groups = Get-MgUserMemberOf -UserId $userObj.Id -All
    $groupsAsOwner = Get-MgUserOwnedObject -UserId $userObj.Id -All


    foreach ($group in $groups) {
        if ($group.AdditionalProperties.'@odata.type' -ne '#microsoft.graph.group') {
            continue
        }

        $groupId = $group.Id
        $groupName = $group.AdditionalProperties.DisplayName

        if (-not $groupId) {
            Write-Host "Skipping group with empty ID"
            continue
        }

        $fullGroup = Get-MgGroup -GroupId $groupId

        if ($fullGroup.GroupTypes -contains "DynamicMembership") {
            Write-Host "Skipping dynamic group: ${groupName}"
            continue
        }

        # Remove as MEMBER
        try {
            Remove-MgGroupMemberByRef -GroupId $groupId -DirectoryObjectId $userObj.Id -ErrorAction Stop
            Write-Host "Removed as MEMBER from ${groupName}"
        }
        catch {
            Write-Host "Failed MEMBER removal from ${groupName}: $($_.Exception.Message)"
        }}
foreach ($group2 in $groupsAsOwner) {
$groupId2 = $group2.Id
$groupName2 = $group2.AdditionalProperties.DisplayName
        # Remove as OWNER
        try {
            Remove-MgGroupOwnerByRef -GroupId $groupId2 -DirectoryObjectId $userObj.Id -ErrorAction Stop
            Write-Host "Removed as OWNER from ${groupName2}"
        }
        catch {
            Write-Host "Failed OWNER removal from ${groupName2}: $($_.Exception.Message)"
        }
    }
    }

# --- PART 2: Exchange Distribution Groups + Mail-enabled Security Groups ---
# Connect to Exchange Online
Connect-ExchangeOnline -ManagedIdentity -Organization ****i.onmicrosoft.com
$dlGroups = Get-DistributionGroup -ResultSize Unlimited

foreach ($dl in $dlGroups) {
    try {
        # Remove as MEMBER
        $members = Get-DistributionGroupMember -Identity $dl.Identity -ResultSize Unlimited
        $memberMatch = $members | Where-Object { $_.PrimarySmtpAddress -ieq $userEmail }

        if ($memberMatch) {
            Remove-DistributionGroupMember -Identity $dl.Identity -Member $userEmail -BypassSecurityGroupManagerCheck -Confirm:$false
            Write-Host "Removed as MEMBER from Exchange group: $($dl.DisplayName)"
        }

        # Remove as OWNER
        # $owners = (Get-DistributionGroup -Identity $dl.Identity).ManagedBy
        $owners = (Get-DistributionGroup -Identity $dl.Identity).ManagedBy | Foreach-Object { Get-Mailbox $_ }
        $ownerMatch = $owners | Where-Object { $_.PrimarySmtpAddress -ieq $userEmail }

        if ($ownerMatch) {
            Set-DistributionGroup -Identity $dl.Identity -ManagedBy @{ Remove = "$userEmail" }
            Write-Host "Removed as OWNER from Exchange group: $($dl.DisplayName)"
        }
    }
    catch {
        Write-Host "Failed Exchange removal for group: $($dl.DisplayName) - $($_.Exception.Message)"
    }
}

Write-Host "Cleanup script completed."

and requirments.psd1

@{
'Az'  =  '13.*'
    'ExchangeOnlineManagement'  =  '3.*'
    'Microsoft.Graph'  =  '2.*'
}

and inside Azure i have those App Files, i can not see the Modules folder:-

[
  {
    "name": ".gitconfig",
    "size": 27,
    "mtime": "2025-05-08T20:08:46.6675183+00:00",
    "crtime": "2025-05-08T20:08:46.6625401+00:00",
    "mime": "application/octet-stream",
    "href": "https://powershell-b**************d.scm.canadacentral-01.azurewebsites.net/api/vfs/.gitconfig",
    "path": "C:\\home\\.gitconfig"
  },
  {
    "name": ".ssh",
    "size": 0,
    "mtime": "2025-05-12T14:24:33.6509983+00:00",
    "crtime": "2025-05-12T14:24:33.6509983+00:00",
    "mime": "inode/directory",
    "href": "https://powershell-b**************d.scm.canadacentral-01.azurewebsites.net/api/vfs/.ssh/",
    "path": "C:\\home\\.ssh"
  },
  {
    "name": "ASP.NET",
    "size": 0,
    "mtime": "2025-05-08T17:54:47.5576215+00:00",
    "crtime": "2025-05-08T17:54:47.5576215+00:00",
    "mime": "inode/directory",
    "href": "https://powershell-b**************d.scm.canadacentral-01.azurewebsites.net/api/vfs/ASP.NET/",
    "path": "C:\\home\\ASP.NET"
  },
  {
    "name": "data",
    "size": 0,
    "mtime": "2025-05-08T17:52:57.5837622+00:00",
    "crtime": "2025-05-08T17:52:57.5837622+00:00",
    "mime": "inode/directory",
    "href": "https://powershell-b**************d.scm.canadacentral-01.azurewebsites.net/api/vfs/data/",
    "path": "C:\\home\\data"
  },
  {
    "name": "gitsafedirectory.marker",
    "size": 0,
    "mtime": "2025-05-08T20:08:46.684444+00:00",
    "crtime": "2025-05-08T20:08:46.684444+00:00",
    "mime": "application/octet-stream",
    "href": "https://powershell-b**************d.scm.canadacentral-01.azurewebsites.net/api/vfs/gitsafedirectory.marker",
    "path": "C:\\home\\gitsafedirectory.marker"
  },
  {
    "name": "LogFiles",
    "size": 0,
    "mtime": "2025-05-08T17:49:45.7843171+00:00",
    "crtime": "2025-05-08T17:49:45.7843171+00:00",
    "mime": "inode/directory",
    "href": "https://powershell-b**************d.scm.canadacentral-01.azurewebsites.net/api/vfs/LogFiles/",
    "path": "C:\\home\\LogFiles"
  },
  {
    "name": "site",
    "size": 0,
    "mtime": "2025-05-08T17:49:45.8042295+00:00",
    "crtime": "2025-05-08T17:49:45.8042295+00:00",
    "mime": "inode/directory",
    "href": "https://powershell-b**************d.scm.canadacentral-01.azurewebsites.net/api/vfs/site/",
    "path": "C:\\home\\site"
  },
  {
    "name": "SystemDrive",
    "size": 0,
    "mtime": "2025-05-19T14:40:33.0113046+00:00",
    "crtime": "2025-03-06T07:41:06.3658452+00:00",
    "mime": "inode/shortcut",
    "href": "https://powershell-b**************d.scm.canadacentral-01.azurewebsites.net/api/vfs/SystemDrive/",
    "path": "C:\\"
  },
  {
    "name": "LocalSiteRoot",
    "size": 0,
    "mtime": "2025-05-20T12:41:27.9051344+00:00",
    "crtime": "2025-05-20T12:41:25.4114883+00:00",
    "mime": "inode/shortcut",
    "href": "https://powershell-b**************d.scm.canadacentral-01.azurewebsites.net/api/vfs/LocalSiteRoot/",
    "path": "C:\\local"
  }
]

and the profile.ps1 , where i commented all the code:-

# Azure Functions profile.ps1
#
# This profile.ps1 will get executed every "cold start" of your Function App.
# "cold start" occurs when:
#
# * A Function App starts up for the very first time
# * A Function App starts up after being de-allocated due to inactivity
#
# You can define helper functions, run commands, or specify environment variables
# NOTE: any variables defined that are not environment variables will get reset after the first execution

# Authenticate with Azure PowerShell using MSI.
# Remove this if you are not planning on using MSI or Azure PowerShell.
#if ($env:MSI_SECRET) {
 #   Disable-AzContextAutosave -Scope Process | Out-Null
  #  Connect-AzAccount -Identity
#}

# Uncomment the next line to enable legacy AzureRm alias in Azure PowerShell.
# Enable-AzureRmAlias

# You can also define functions or aliases that can be referenced in any of your PowerShell functions.

Solution

  • Below worked for me, refer the solutions:

    Edit:

    Run below commands:

    mkdir Modules
    cd Modules
    Save-Module -Name module-you-want -Path Modules
    Save-Module -Name other-module-you-want -Path Modules
    

    Deploy the function using below command

    func azure functionapp publish functionappname
    
    PS C:\Users\uname\psfn> func azure functionapp publish kpsfn
    Getting site publishing info...
    [2025-05-12T12:00:14.582Z] Starting the function app deployment...
    Creating archive for current directory...
    Uploading 186.22 MB [#############################################################################]
    Creating archive for current directory...
    Uploading 186.22 MB [#############################################################################]
    Upload completed successfully.
    Deployment completed successfully.
    [2025-05-12T12:21:17.009Z] Syncing triggers...
    Functions in kpsfn:
        TimerTrigger - [timerTrigger]
    

    You should be able to see the Modules folder in KUDU site under SIte/wwwroot after deploying the function code to Azure.

    Update:

    Follow below steps to resolve the Import-Module error:

    Connect-ExchangeOnline -ManagedIdentity -Organization <yourdomain>.onmicrosoft.com
    

    Use below modified code:

    param($Timer)
    
    # Connect to Microsoft Graph with required delegated scopes
     Write-Host "Start"
    Connect-MgGraph -Identity
     Write-Host "Connected to Graph"
    # Connect to Exchange Online
    Connect-ExchangeOnline -ManagedIdentity -Organization <yourdomain>.onmicrosoft.com
    Write-Host "Connected to Graph"
    # Target user email
    $userEmail = "usermail" 
    
    # --- PART 1: Azure AD + Office 365 Groups ---
    $userList = Get-MgUser -Filter "mail eq '$userEmail' or userPrincipalName eq '$userEmail'"  -ConsistencyLevel eventual -All -Property Id, DisplayName, UserPrincipalName, Mail
    
    $userObj = $userList | Select-Object -First 1
    
    if (-not $userObj) {
        Write-Host "User $userEmail not found in Microsoft Graph. Skipping AAD removal."
    } elseif (-not $userObj.Id) {
        Write-Host "User objectId missing even after lookup. Skipping AAD removal."
    } else {
        Write-Host "Found user: $($userObj.DisplayName) ($($userObj.UserPrincipalName))"
    
        $groups = Get-MgUserMemberOf -UserId $userObj.Id -All
        $groupsAsOwner = Get-MgUserOwnedObject -UserId $userObj.Id -All
    
    
        foreach ($group in $groups) {
            if ($group.AdditionalProperties.'@odata.type' -ne '#microsoft.graph.group') {
                continue
            }
    
            $groupId = $group.Id
            $groupName = $group.AdditionalProperties.DisplayName
    
            if (-not $groupId) {
                Write-Host "Skipping group with empty ID"
                continue
            }
    
            $fullGroup = Get-MgGroup -GroupId $groupId
    
            if ($fullGroup.GroupTypes -contains "DynamicMembership") {
                Write-Host "Skipping dynamic group: ${groupName}"
                continue
            }
    
            # Remove as MEMBER
            try {
                Remove-MgGroupMemberByRef -GroupId $groupId -DirectoryObjectId $userObj.Id -ErrorAction Stop
                Write-Host "Removed as MEMBER from ${groupName}"
            }
            catch {
                Write-Host "Failed MEMBER removal from ${groupName}: $($_.Exception.Message)"
            }}
    foreach ($group2 in $groupsAsOwner) {
    $groupId2 = $group2.Id
    $groupName2 = $group2.AdditionalProperties.DisplayName
            # Remove as OWNER
            try {
                Remove-MgGroupOwnerByRef -GroupId $groupId2 -DirectoryObjectId $userObj.Id -ErrorAction Stop
                Write-Host "Removed as OWNER from ${groupName2}"
            }
            catch {
                Write-Host "Failed OWNER removal from ${groupName2}: $($_.Exception.Message)"
            }
        }
        }
    
    # --- PART 2: Exchange Distribution Groups + Mail-enabled Security Groups ---
    $dlGroups = Get-DistributionGroup -ResultSize Unlimited
    
    foreach ($dl in $dlGroups) {
        try {
            # Remove as MEMBER
            $members = Get-DistributionGroupMember -Identity $dl.Identity -ResultSize Unlimited
            $memberMatch = $members | Where-Object { $_.PrimarySmtpAddress -ieq $userEmail }
    
            if ($memberMatch) {
                Remove-DistributionGroupMember -Identity $dl.Identity -Member $userEmail -BypassSecurityGroupManagerCheck -Confirm:$false
                Write-Host "Removed as MEMBER from Exchange group: $($dl.DisplayName)"
            }
    
            # Remove as OWNER
            # $owners = (Get-DistributionGroup -Identity $dl.Identity).ManagedBy
            $owners = (Get-DistributionGroup -Identity $dl.Identity).ManagedBy | Foreach-Object { Get-Mailbox $_ }
            $ownerMatch = $owners | Where-Object { $_.PrimarySmtpAddress -ieq $userEmail }
    
            if ($ownerMatch) {
                Set-DistributionGroup -Identity $dl.Identity -ManagedBy @{ Remove = "$userEmail" }
                Write-Host "Removed as OWNER from Exchange group: $($dl.DisplayName)"
            }
        }
        catch {
            Write-Host "Failed Exchange removal for group: $($dl.DisplayName) - $($_.Exception.Message)"
        }
    }
    
    Write-Host "Cleanup script completed."
    

    requirements.ps1:

    @{
        'Az'  =  '13.*'
        'ExchangeOnlineManagement'  =  '3.*'
        'Microsoft.Graph'  =  '2.*'
    }
    

    Able to run the commands successfully:

    enter image description here

    To connect to Exchange Online, you need to assign Administrative Role to the Managed Identity and grant Admin Consent to the Service Principal, refer the blog for clear steps.