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:-
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.
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
Mention Modules folder in .gitignore
file.
Remove Az in requirements.ps1
.
Build the function before deploying.
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:
Remove Import-Module
statements in run.ps1
:
Connect-ExchangeOnline
doesn't support -Identity
parameter, use the below command instead:
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:
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.