Powershell: Working as script, but not as a module - powershell

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
}
}

Related

canceling get-credentials in loop

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

Removing OnPremise Shared Mailbox

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

Why is Powershell not importing the exchange Commandlets after removing all Pssession's and adding a new one?

Context
I created a little Powershell Script that lets you change between 2 Exchange Server (Pssession to the 2 servers).
In the Begin part of the script I remove all existing pssessions (it's on to the default Exchange 2010 server).
# Remove all Ps-Sesssions
Get-PSSession | Remove-PSSession
Then in the Process part I do this:
Process
{
# Choose Server:
# Default server
$server = "2010"
if ($ExchangeVersion -eq "2016") {
$server = "2217ex0010at01"
} elseif ($ExchangeVersion -eq "2010") {
$server = "2217exlimbx01"
} else {
Write-Host "Server wurde nicht gefunden, gibt 2010 oder 2016 ein"
}
# Add Pssession and import it
$s = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://$server/powershell
Import-PSSession $s -AllowClobber
}
The problem
When I start up powershell, it imports a pssession to the 2010 server. Everything is working fine. I have all commandlets like get-mailbox
But when I run the script like this Set-ExchangeServer -ExchangeVersion "2016" everything appears to be fine. But then when I try any exchange commandlet it won't exist.
What have I tried
I manually typed in the code I have in the script to import the session to 2016. And thats the weird part. When I'm doing it manually it works and I have all commandlets.
What could be the cause of this issue?
I did for myself simple functions to remove the previous exchange sessions, it might help you.
Probably you did'nt remove the tmp*... Module...
For On-Premise:
Function Remove-LocalExchangeShell
{
if ($LocalSession = Get-PSSession | ? {$_.ComputerName -match 'exchSvrName'}) {Remove-PSSession $LocalSession}
if ($TmpModule = Get-Module -Name tmp*) {Remove-Module $TmpModule}
}
For Office365:
Function Remove-365ExchangeShell
{
if ($365Session = Get-PSSession | ? {$_.ComputerName -match 'Outlook'}) {Remove-PSSession $365Session}
if ($TmpModule = Get-Module -Name tmp*) {Remove-Module $TmpModule}
}
Run the one you need before executing the New-PSSession
I think the root cause is a question of scope.
Try changing $s = to $global:s = so that the session is available outside of the script.

Create a script which disables a Windows Account on a target host. Only Admin can execute this action

I want to create a PowerShell script which will disable the windows account, the target host name will be provided as an argument. Only admin should be able to execute this task.
This is what I have tried. Could someone please tell me if this approach is right or is there any better way to do this.
param( [Parameter(Mandatory=$true)] [String] $TargetHost ,
[Parameter(Mandatory=$true)] [String] $TargetUserName ,
[String] $User ,
[String] $Password)
# Set up a trap to properly exit on terminating exceptions
trap [Exception] {
write-error $("TRAPPED: " + $_)
exit 1
}
function DeactivateAccount($TargetHost , $TargetUserName ,$User , $Password){
$TargetHost = $TargetHost #Target Host on which windows account deactivation will be done.
$TargetUserName = $TargetUserName #User Name of Target.
$Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() #Domain name of the localhost.
$localHost = [System.Net.Dns]::GetHostName()
$localIP = [System.Net.Dns]::GetHostAddresses("$localHost")
#if TargetHost and LocalHost are same.
if($localHost -like $TargetHost -OR $localIP -like $TargetHost) {
if($Domain -eq [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()){
$process = net user $TargetUsername /domain /active:no #Performs the operation on the domain controller in the computer's primary domain.
} else {
$process = net user $TargetUsername /active:no
}
Write-host " $TargetUsername account deactivated "
}
#If TargetHost is remote Host.
else {
$User = $User #Creds to perform admin function.
$Password = $Password
$SecurePassword = new-Object System.Security.SecureString #Convert password into secure string.
$Password.ToCharArray() | % { $SecurePassword.AppendChar($_) }
$Cred = New-Object -typename System.Management.Automation.PSCredential -argumentlist "$User",$securePassword
$newSession = New-PSSession -ComputerName "$TargetHost" -credential $Cred #Used PSSession for persistent connection and credentials to Specify a user account that has permission to perform this action.
$export_username = Invoke-Command -Session $newSession -ScriptBlock {$username=args[1]} # Invoke-Command command uses the Session parameter(here newSession) to run the commands in same session.
if($Domain -eq [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()){
$process = Invoke-Command -Session $newSession -ScriptBlock {net user $username /domain /active:no}
} else {
$process = Invoke-Command -Session $newSession -ScriptBlock {net user $username /active:no}
}
Write-host " $TargetUsername account deactivated "
Remove-PSSession $newSession # Closes Windows PowerShell sessions.
}
if(-not $?) { # Returns true if last command was successful.
Write-Error "Windows Deactivation Failed!!"
exit 1
}
}
DeactivateAccount($TargetHost , $TargetUserName ,$User , $Password)
Couple of things:
Your meant to show some code to show you tried but since you're new to Powershell I'll let that slide :)
Is it a local windows account you are trying to disable or an AD one? For the purpose of this I'll assume local.
Grab this module: https://gallery.technet.microsoft.com/PowerShell-Module-to-255637a3
The dude basically made a module for exactly what you want to do :)
Note: If you have Powershell 5.1+ you won't need the module they added new cmdlets to do this natively.
Credential-wise I wouldn't worry, Powershell can't bypass windows security, it will execute with the permissions of the user that ran the script unless your script specifically gives credentials for another user in the commands.
Let me know how you get on.

Monitoring Services on an Azure VM using an Azure Runbook

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.