Below is the script I am trying to execute to remove Shared Mailboxes. We have on-prem as well as oncloud shared mailboxex, the below script works fine for oncloud shared mailboxes, but it does not work for on-prem, even though it shows successful execution.
Should I be using Remove-ADUser instead of Remove-RemoteMailbox?
Try{
$SharedMailboxUPN = $payload.sharedMailboxUPN
$isOnPrem = ""
#Connect to Exchange-Online
Try
{ #FIND OUT If Shared Mailbox is On-Prem or OnCloud
$isOnPrem = Get-Mailbox -Identity $SharedMailboxUPN | Select-Object -ExpandProperty RemoteRecipientType
}
Catch
{
#ERROR MESSAGE GOES HERE
}
if($isOnPrem -eq "Migrated, SharedMailbox") #OnPrem Mailbox
{
$sessionCheck = Get-PSSession
##### Connect to Exchange On-Prem Powershell #####
if ($sessionCheck -eq $null)
{
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://$TargetServer/PowerShell/ -Authentication Kerberos -Credential $cred
Import-PSSession $Session
}
}
Try
{
Remove-RemoteMailbox -Identity $SharedMailboxUPN -Confirm:$false -ErrorAction Stop #Shows result as Success, but mailbox is not removed for some reason
Remove-ADUser -Identity $SharedMailboxUPN #SHOULD I USE this instead??
}
Catch
{
#ERROR MESSAGE GOES HERE
}
}
else #oncloud mailbox
{
Try
{
#Connect to ExchangeOnline
Remove-Mailbox -Identity $SharedMailboxUPN -Confirm:$false -ErrorAction Stop
}
Catch
{
#ERROR MESSAGE GOES HERE
}
}
}
}
Catch
{
#ERROR MESSAGE GOES HERE
}
Remove-RemoteMailbox will only work for your cloud shared mailboxes, you would want Remove-Mailbox to remove on prem shared mailboxes. Using Remove-ADUser will remove the object but you should really use the Exchange cmdlets to do this or you could end up with unexpected results like orphaned mailboxes etc
Related
I am working on developing PowerShell script to automate a task on a remote server by using Invoke-Command with WinRM.
The script will take the server IP, test WinRM and "Get-Credential" cmdlet to establish session and use Invoke-Command to run another script on remote server. I have made significant progress of what I want to achieve, however, I am having trouble on how to setup the code so that when I press the "Cancel" or "X" button on Get-Credential prompt it should abort the script and return to the regular PowerShell command line prompt.
Below is what I have so far, I have erased the comments and description of the code to keep the number of words less in here.
function SS
{
Add-Type -AssemblyName System.Windows.Forms
$BInput = [System.Windows.Forms.MessageBox]::Show('Do you want to proceed?', 'Confirmation',[System.Windows.Forms.MessageBoxButtons]::YesNo)
switch ($BInput)
{
"Yes" {
while ($true)
{
$server=Read-Host "Enter Server IP Address"
set-item -Path WSMan:\localhost\Client\TrustedHosts -Value "$server" -Force
if(Test-WSMan -ComputerName $server -ErrorAction SilentlyContinue)
{
Write-Host "$server is accessible, enter credentials to connect"
while ($true)
{
$creden=Get-Credential -Message "Please enter the server credentials that you want to connect"
$serversession = New-Pssession -ComputerName $server -Credential $creden -ErrorAction SilentlyContinue
if(-not($serversession))
{
write-warning "Credentials are not valild, please try again"
}
else
{
write-host "$server is connected, starting the workflow ......"
Invoke-Command -Session $serversession -FilePath "C:\Temp\XXX.ps1"
}
}
Break
}
else
{
write-host "Windows Remote Management (WinRM) protocol is not running, please check service and confirm."
}
}
Get-Pssession | Remove-PSSession
}
"No" {
Break
}
}
}
I understand I have to apply the changes / logic after this line
$creden=Get-Credential -Message "Please enter the server credentials that you want to connect"
But can't seem to find it yet. I looked online and have taken different approaches but no success so far. I would like to have opinions or recommendations on how to tackle this, appreciate your help.
Thanks
What i'm seeing is that you may be thinking too much into it. A simple if statement should do the trick, try:
$creden=Get-Credential -Message "Please enter the server credentials that you want to connect"
if(!$creden){break}
Continuing from my comment.
Try this refactor of your use case.
Point of note: Note fully tested since I do not have an environment at this time to test.
Function Start-WorkFlow
{
<#
.Synopsis
Execute a workflow
.DESCRIPTION
Sets up a WinRM session to a remote host to execute the defined workflow
.EXAMPLE
Start-WorkFlow
.EXAMPLE
swf
.INPUTS
Remote host IPAddress
Remove host credentials
.OUTPUTS
Resutls of teh workflow
.NOTES
V 0.0.1 - Prototype script. Clean-Up before production use
.COMPONENT
Stand-alone script
.ROLE
Administrative actions
.FUNCTIONALITY
Implemetned error logic for each code block
Restrict the user input to only be a proper IPAddress
Validate TCPIP state
Validate WSman state
Establish a new session
Process workflow
Exit session
#>
[cmdletbinding(SupportsShouldProcess)]
[Alias('swf')]
Param
(
)
If ((Read-Host -Prompt 'Do you want to proceed: [Yes/No]') -eq 'No' )
{Break}
Else
{
Do {$RemoteServerIPAddress = (Read-Host -Prompt 'Enter Server IP Address')}
Until ($RemoteServerIPAddress -match "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
Get-ChildItem -Path 'WSMan:\localhost\Client\TrustedHosts'
Try
{
(Test-Connection -ComputerName $RemoteServerIPAddress -Count 1 -ErrorAction Stop).IPV4Address
# Set-Item -Path 'WSMan:\localhost\Client\TrustedHosts' -Value $RemoteServerIPAddress -Force
Get-ChildItem -Path 'WSMan:\localhost\Client\TrustedHosts'
Try
{
Test-WSMan -ComputerName $RemoteServerIPAddress -ErrorAction Stop
"$RemoteServerIPAddress is accessible, enter credentials to connect"
Do
{
$Creds = $null
$CredMesssage = 'Please enter the remote server credentials that you want to connect.'
$CredMesssage = "$CredMesssage If credentials are not valid, you will be prompted to re-enter them."
$Creds = Get-Credential -Message $CredMesssage
if(-Not $creds)
{
Write-Warning -Message 'Credential request cancelled.'
Start-Sleep -Seconds 3
Exit
}
$NewPSSessionSplat = #{
ComputerName = $RemoteServerIPAddress
Credential = $Creds
Name = 'RemoteSessionName'
ErrorAction = 'Stop'
}
New-PSSession $NewPSSessionSplat
}
Until (Get-PSSession -Name 'RemoteSessionName')
"$RemoteServerIPAddress is connected, starting the workflow ......"
Invoke-Command -Session $RemoteServerSession -FilePath 'C:\Temp\XXX.ps1'
}
Catch
{
Write-Warning -Message 'Session connection results:'
$PSitem.Exception.Message
}
Finally
{
Get-PSSession |
Remove-PSSession -ErrorAction SilentlyContinue
}
}
Catch
{
Write-Warning -Message "
The remote server $RemoteServerIPAddress is not available
Exiting the session."
Start-Sleep -Seconds 3
Exit
}
}
}
Start-WorkFlow
I have written a simple code to connect the console to different clients and different services with two switch options
First: select the customer/client
Second: select the service (Azure AD/Ex Online)
When I run this as a script, it works without any issues.
When I run this as a module, it still works to the point that when I select Exchange Online, it will login with the saved credentials and "import" some data.
But for example Get-Mailbox is no recognized command - only if I run this as a script - though the "connection" to O365 is alive. In this step I have to redo the Import-PSSession again to get those commands working.
Can somebody tell me what I've done or understood wrong, here?
Thanks.
<#
.SYNOPSIS
Easy connect to different O365 Modules
.DESCRIPTION
Use the different options to connect to O365: AzureAD, Exchange Online, etc.
.PARAMETER Client
Enter the client you are trying to connect to.
BUD (****)
MEN (****)
.PARAMETER Service
Enter the service you need.
Option 1: AAD (Azure AD)
Option 2: Exchange (Exchange Online)
.EXAMPLE
Connect-O365 -Client BUD
.NOTES
Written by: Daniel Leiner
Version: 1.2
Date: 05/07/2019
#>
param(
[parameter(mandatory=$false)][string]$Client,
[parameter(mandatory=$false)][string]$Service
)
if ([string]::IsNullOrEmpty($Client)) {
$Client = switch (Read-Host "Hello, which client you want to connect to?" `n "1) BUD "`n "2) Men" `n "3) Different"`n)
{
1 { "BUD"; break }
2 { "MEN"; break }
3 { "Unknown"; break }
default { "Inkorrekte Eingabe" }
}
}
If ($Client -eq "BUD")
{$Cred = Get-StoredCredential -User admin#************}
Elseif ($Client -eq "MEN")
{$Cred = Get-StoredCredential -User admin#*************}
Elseif ($Client -eq "Unknown")
{ $Cred = Get-Credential" }
if ([string]::isNullorEmpty($Service)) {
switch (Read-Host "Where do you want to connect to?" `n "a) AzureAD" `n "b) Exchange Online" `n)
{
a { $service = "AAD"; break }
b { $service = "Exchange"; break }
default { "Incorrect Input." }
}
}
if ($service -eq "AAD")
{ Connect-AzureAD -Credential $cred }
elseif ($service -eq "Exchange")
{ $global:worksession = New-PSSession -ConfigurationName Microsoft.Exchange -Credential $cred -ConnectionUri https://outlook.office365.com/powershell-liveid `
-Authentication Basic -AllowRedirection
Import-PSSession $global:worksession -AllowClobber -DisableNameChecking
}
}
I'm trying to export a list of all users with no photo from our Exchange Online account using powershell. I cannot get it to work and have tried various methods.
Get-UserPhoto returns this exception when there is no profile present.
Microsoft.Exchange.Data.Storage.UserPhotoNotFoundException: There is no photo stored here.
First of all I tried use Errorvariable against the command but received:
A variable that cannot be referenced in restricted language mode or a Data section is being referenced. Variables that can be referenced include the following: $PSCulture, $PSUICulture, $true, $false, and $null.
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : VariableReferenceNotSupportedInDataSection
+ PSComputerName : outlook.office365.com
Next I tried try, catch but the non-terminating error never calls the catch despite various methods followed online about setting $ErrorActionPreference first of all.
Any ideas ? Here is the script:
$UserCredential = Get-Credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
Import-PSSession $Session
$timestamp = $timestamp = get-date -Format "dd/M/yy-hh-mm"
$outfile = "c:\temp\" + $timestamp + "UserswithoutPhotos.txt"
$resultslist=#()
$userlist = get-user -ResultSize unlimited -RecipientTypeDetails usermailbox | where {$_.accountdisabled -ne $True}
Foreach($user in $userlist)
{
try
{
$user | get-userphoto -erroraction stop
}
catch
{
Write-Host "ERROR: $_"
$email= $user.userprincipalname
$name = $user.DisplayName
$office = $user.office
write-host "User photo not found...adding to list : $name , $email, $office"
$resultslist += $user
}
}
$resultslist | add-content $outfile
$resultslist
PowerShell's error handling is notoriously tricky, especially so with cmdlets that use implicit remoting via a locally generated proxy script module.
The following idiom provides a workaround based on temporarily setting $ErrorActionPreference to Stop globally (of course, you may move the code for restoring the previous value outside the foreach loop), which ensures that even functions from different modules see it:
try {
# Temporarily set $ErrorActionPreference to 'Stop' *globally*
$prevErrorActionPreference = $global:ErrorActionPreference
$global:ErrorActionPreference = 'Stop'
$user | get-userphoto
} catch {
Write-Host "ERROR: $_"
$email= $user.userprincipalname
$name = $user.DisplayName
$office = $user.office
write-host "User photo not found...adding to list : $name, $email, $office"
$resultslist += $user
} finally {
# Restore the previous global $ErrorActionPreference value
$global:ErrorActionPreference = $prevErrorActionPreference
}
As for why this is necessary:
Functions defined in modules do not see the caller's preference variables - unlike cmdlets, which do.
The only outside scope that functions in modules see is the global scope.
For more information on this fundamental problem, see this GitHub issue.
You can throw your own error like so:
try {
$error.Clear()
$user | Get-UserPhoto
if ($error[0].CategoryInfo.Reason -eq "UserPhotoNotFoundException") {throw "UserPhotoNotFoundException" }
} catch {
#code
}
I'm trying to export a list of all users with no photo from our Exchange Online account using powershell. I cannot get it to work and have tried various methods.
Get-UserPhoto returns this exception when there is no profile present.
Microsoft.Exchange.Data.Storage.UserPhotoNotFoundException: There is no photo stored here.
First of all I tried use Errorvariable against the command but received:
A variable that cannot be referenced in restricted language mode or a Data section is being referenced. Variables that can be referenced include the following: $PSCulture, $PSUICulture, $true, $false, and $null.
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : VariableReferenceNotSupportedInDataSection
+ PSComputerName : outlook.office365.com
Next I tried try, catch but the non-terminating error never calls the catch despite various methods followed online about setting $ErrorActionPreference first of all.
Any ideas ? Here is the script:
$UserCredential = Get-Credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
Import-PSSession $Session
$timestamp = $timestamp = get-date -Format "dd/M/yy-hh-mm"
$outfile = "c:\temp\" + $timestamp + "UserswithoutPhotos.txt"
$resultslist=#()
$userlist = get-user -ResultSize unlimited -RecipientTypeDetails usermailbox | where {$_.accountdisabled -ne $True}
Foreach($user in $userlist)
{
try
{
$user | get-userphoto -erroraction stop
}
catch
{
Write-Host "ERROR: $_"
$email= $user.userprincipalname
$name = $user.DisplayName
$office = $user.office
write-host "User photo not found...adding to list : $name , $email, $office"
$resultslist += $user
}
}
$resultslist | add-content $outfile
$resultslist
PowerShell's error handling is notoriously tricky, especially so with cmdlets that use implicit remoting via a locally generated proxy script module.
The following idiom provides a workaround based on temporarily setting $ErrorActionPreference to Stop globally (of course, you may move the code for restoring the previous value outside the foreach loop), which ensures that even functions from different modules see it:
try {
# Temporarily set $ErrorActionPreference to 'Stop' *globally*
$prevErrorActionPreference = $global:ErrorActionPreference
$global:ErrorActionPreference = 'Stop'
$user | get-userphoto
} catch {
Write-Host "ERROR: $_"
$email= $user.userprincipalname
$name = $user.DisplayName
$office = $user.office
write-host "User photo not found...adding to list : $name, $email, $office"
$resultslist += $user
} finally {
# Restore the previous global $ErrorActionPreference value
$global:ErrorActionPreference = $prevErrorActionPreference
}
As for why this is necessary:
Functions defined in modules do not see the caller's preference variables - unlike cmdlets, which do.
The only outside scope that functions in modules see is the global scope.
For more information on this fundamental problem, see this GitHub issue.
You can throw your own error like so:
try {
$error.Clear()
$user | Get-UserPhoto
if ($error[0].CategoryInfo.Reason -eq "UserPhotoNotFoundException") {throw "UserPhotoNotFoundException" }
} catch {
#code
}
I have a Powershell script that enumerates running services and their current state using Get-WmiObject Win32_Service. Initial version based on this one and then modified for Azure. When I run the script in Powershell (without the azure automation parts) on my location machine it works fine and I can connect to all the machines of interest, but when I port it to a runbook i get the following error: "Get-WmiObject : The RPC server is unavailable."
Q: Is the problem with permissions for the automation account? If so, what account should I add to the local machines to resolve the issue?
Q: Is Get-WmiObject not a valid way to initiate the connection? If not, what should I try instead?
The code I'm using is below:
[CmdletBinding(SupportsShouldProcess = $true)]
param(
# Servers to check
[Parameter(Mandatory=$true)][string[]]$ServerList,
# Services to check for
[Parameter(Mandatory=$true)][string[]]$includeService
)
# Following modifies the Write-Verbose behavior to turn the messages on globally for this session
$VerbosePreference = "Continue"
$connectionName = "AzureRunAsConnection"
# retry
$retry = 6
$syncOk = $false
$servicePrincipalConnection = Get-AutomationConnection -Name $connectionName
do
{
try
{
Add-AzureRmAccount -ServicePrincipal -TenantId $servicePrincipalConnection.TenantId -ApplicationId $servicePrincipalConnection.ApplicationId -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
$syncOk = $true
}
catch
{
$ErrorMessage = $_.Exception.Message
$StackTrace = $_.Exception.StackTrace
Write-Warning "Error during sync: $ErrorMessage, stack: $StackTrace. Retry attempts left: $retry"
$retry = $retry - 1
Start-Sleep -s 60
}
} while (-not $syncOk -and $retry -ge 0)
Select-AzureRMSubscription -SubscriptionId $SubscriptionId -TenantId $servicePrincipalConnection.TenantId
$currentSubscription = Get-AzureRMSubscription -SubscriptionId $SubscriptionId -TenantId $servicePrincipalConnection.TenantId
Set-AzureRmContext -SubscriptionId $SubscriptionId;
$props=#()
[System.Collections.ArrayList]$unreachableServers = #()
Foreach($ServerName in ($ServerList))
{
try
{
$service = Get-WmiObject Win32_Service -ComputerName $servername
}
catch
{}
if ($Service -ne $NULL)
{
foreach ($item in $service)
{
#$item.DisplayName
Foreach($include in $includeService)
{
#write-host $include
if(($item.name).Contains($include) -eq $TRUE)
{
$props += [pscustomobject]#{
servername = $ServerName
name = $item.name
Status = $item.Status
startmode = $item.startmode
state = $item.state
serviceaccount=$item.startname
DisplayName =$item.displayname}
}
}
}
}
else
{
Write-host "Failed to contact server: "$ServerName
$unreachableServers.Add($ServerName)
}
}
$props | Format-Table Servername,Name,startmode,state,serviceaccount,displayname -AutoSize
I am assuming that you are using the Azure Automation Hybrid Worker functionality. Be default it runs under the System account. However you can use a different account to run the runbook under. This is documented here: Azure Automation Hybrid Worker; Look under the RunAs account section. Use the same account that works when you try it directly.
have you considered using OMS? this sounds like a better thing to do.
Anyway, to answer your questions, I would probably create a local user and create a PS configuration endpoint for that user to connect to, and connect impersonating that user from the Automation Account, but again, I wouldn't even go this route, I'd rather use OMS.