Connect-MsolService - providing credentials without exposing password in script - powershell

I wish to run the below PowerShell script as a scheduled task to pull provisioning logs from Azure AD. However, I do not wish to embed the password. I appreciate this is not a problem specific to PowerShell or Microsoft Online. What technique can I use to not have the password stored as clear text? Thanks
Script credits go to: Pawel Janowicz
$AzureUsername = 'log.reader#tenant.net'
$Password = "xxxxxx"
$SecureString = ConvertTo-SecureString -AsPlainText $Password -Force
$SecuredCreds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AzureUsername,$SecureString
$OutputCSV = "$Env:USERPROFILE\desktop\DirSyncProvisioningErrors_$(Get-Date -Format "yyyyMMdd").csv"
###### Connecting ############################################################################################################
Try{
[void] (Connect-MsolService -Credential $SecuredCreds)
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $SecuredCreds -Authentication Basic -AllowRedirection
[void] (Import-PSSession $Session -DisableNameChecking)
}
Catch{
$_.Exception.Message
Read-Host 'Press enter to close the window'
Remove-PSSession $Session
Exit
}
###### Getting errors ########################################################################################################
If(Get-MsolHasObjectsWithDirSyncProvisioningErrors){
Try{
$Errors = Get-MsolDirSyncProvisioningError -All | select DisplayName,ObjectID,ObjectType,ProvisioningErrors
$Results = Foreach ($i in $Errors){
$AllErrors = $i.ProvisioningErrors
$AllErrors | %{
$ErrorItem = $_
Get-AzureADObjectByObjectId -ObjectIds $i.objectid | Foreach{
New-Object PSObject -Property ([ordered]#{
'Displayname' = $i.displayname
'ObjectType' = $i.ObjectType
'Attribute' = $ErrorItem.propertyname
'Conflicting value' = $ErrorItem.propertyvalue
})
}
}
}
}
Catch{
$_.Exception.Message
Read-Host 'Press enter to close the window'
Remove-PSSession $Session
Exit
}
}
###### Results ###############################################################################################################
If($Results){
$Results | Format-Table -AutoSize
#Exporting CSV
$Results | Export-CSV $OutputCSV -NoTypeInformation -Force
}
Remove-PSSession $Session

Thank You Theo for providing your suggestion as a comment. Making this as answer to help other community member.
I have executed the command and getting a display to enter the password rather than it was manually provided in script itself.
$SecuredCreds = Get-Credential -UserName 'log.reader#tenant.net' -Message "Please enter credentials"
$OutputCSV = "$Env:USERPROFILE\desktop\DirSyncProvisioningErrors_$(Get-Date -Format "yyyyMMdd").csv"
###### Connecting ############################################################################################################
Try{
[void] (Connect-MsolService -Credential $SecuredCreds)
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $SecuredCreds -Authentication Basic -AllowRedirection
[void] (Import-PSSession $Session -DisableNameChecking)
}
Catch{
$_.Exception.Message
Read-Host 'Press enter to close the window'
Remove-PSSession $Session
Exit
}
###### Getting errors ########################################################################################################
If(Get-MsolHasObjectsWithDirSyncProvisioningErrors){
Try{
$Errors = Get-MsolDirSyncProvisioningError -All | select DisplayName,ObjectID,ObjectType,ProvisioningErrors
$Results = Foreach ($i in $Errors){
$AllErrors = $i.ProvisioningErrors
$AllErrors | %{
$ErrorItem = $_
Get-AzureADObjectByObjectId -ObjectIds $i.objectid | Foreach{
New-Object PSObject -Property ([ordered]#{
'Displayname' = $i.displayname
'ObjectType' = $i.ObjectType
'Attribute' = $ErrorItem.propertyname
'Conflicting value' = $ErrorItem.propertyvalue
})
}
}
}
}
Catch{
$_.Exception.Message
Read-Host 'Press enter to close the window'
Remove-PSSession $Session
Exit
}
}
###### Results ###############################################################################################################
If($Results){
$Results | Format-Table -AutoSize
#Exporting CSV
$Results | Export-CSV $OutputCSV -NoTypeInformation -Force
}
Remove-PSSession $Session

Related

Restart machine using powershell script

I am running the below code but the restart is not working. My intention was to run restart command parallelly on all remote machines at once.
$YourFile = gc "machinelst.txt"
$username = "user1"
$password = "pass1"
$secpw = ConvertTo-SecureString $password -AsPlainText -Force
$cred = New-Object Management.Automation.PSCredential ($username, $secpw)
foreach ($computer in $YourFile)
{
Invoke-Command -ComputerName $computer -credential $cred -ErrorAction Stop -ScriptBlock { Restart-Computer -ComputerName $computer -Force } -AsJob
}
That looks like its the output from Get-Job - could you try Receive-Job $id (Receive-Job 80).
Should give you the actual exception.
This likely runs in parallel just like invoke-command does with an array of computernames:
restart-computer computer01,computer02,computer03,computer04,computer05
Or this. It takes a couple minutes for the winrm service to come back, but they all seem to reboot at the same time.
$c = get-credential
$list = 1..10 | % tostring computer00
restart-computer $list -wait -protocol wsman -cr $c
try this (you will can add -asjob if it's work) :
$username = "yourdomain\user1"
$password = ConvertTo-SecureString "pass1" -AsPlainText -Force
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $password
get-content "machinelst.txt" | %{
Restart-Computer -ComputerName $_ -Authentication default -Credential $cred
}
if you want use job, you can do it :
$listjob=#()
get-content "machinelst.txt" | %{
$listjob+=Restart-Computer -ComputerName $_ -Authentication default -Credential $cred -AsJob
}
$listjob | Wait-Job -Timeout 30
$listjob | %{
if ($_.State -eq 'Failed' )
{
Receive-Job -Job $_ -Keep
}
}

Handling PSSessions in PS script

So my issue is that this script will run fine the first time but if I try running it again, the PSSessions are still active despite the
Get-PSSession | Remove-PSSession
lines. I've tried other methods like calling the computernames or instanceIDs but still won't close them. I'm not sure why the sessions aren't closing but it's the last thing that is keeping this script from working correctly.
Also this is made from the Microsoft article here: https://support.microsoft.com/en-us/help/2956029/migrationpermanentexception-cannot-find-a-recipient-that-has-mailbox-g
#Exchange session for EOL
function EOLExchange-Session {
Write-Host "Importing Exchange Scripting Module.....please wait a few seconds"
Write-Host "NOTE: Login with your username#aklsj.com credentials for Office 365" -ForegroundColor red -BackgroundColor white
pause
$counter = 0
while ($counter -lt 3) {
try {
$EOLPSSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell-liveid?DelegatedOrg=yalldontneedtoknow.com -Authentication Basic -AllowRedirection -Credential $UserCredential -ea stop
write-host "success"
$counter = 10
}
catch {
write-host "failed"
$counter++
if ($counter -ge 3) {
print "Too many attempts"
exit
}
}
}
Import-PSSession $EOLPSSession -AllowClobber -DisableNameChecking -CommandName Get-Mailbox, Set-Mailbox
$SessionID = $EOLPSSession.InstanceId
Write-Host "-------------Instance ID = " $PSSession.InstanceId
Write-Host "-------------Exchange-Session ID = " $SessionID
$SessionID
}
#Exchange session for On-Prem
function OPExchange-Session {
add-pssnapin Microsoft.Exchange.Management.PowerShell.E2010 -ErrorAction Stop
#Write-Host "Importing Exchange Scripting Module.....please wait a few seconds"
#Connect to Exchange using Remote Shell <-- allows Exchange commands in this script
$OPPSSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://mindyabusiness.com/PowerShell/ -Authentication Kerberos -AllowRedirection
Import-PSSession $OPPSSession -AllowClobber -DisableNameChecking -CommandName Get-RemoteMailbox, Set-RemoteMailbox
$SessionID = $OPPSSession.InstanceId
#Write-Host "-------------Instance ID = "$PSSession.InstanceId
#Write-Host "-------------Exchange-Session ID = "$SessionID
$SessionID
}
Function End-Script($SessionID, $f_runtype) {
Write-Host "Log File: $LogFile"
Write-Host ""
if ($SessionID -ne $null) {
$s = Get-PSSession -InstanceId $SessionID
Remove-PSSession -Session $s
}
if((Get-Content $LogFile) -eq $Null) {
Remove-Item $LogFile
} else {
#Set Log File to Read Only
Set-ItemProperty -Path $LogFile -Name IsReadOnly -Value $true
}
Write-Host "Script Complete" -ForegroundColor Gray
Read-Host "Press enter to close the script"
exit
}
Function SyncADConnect {
$s = New-PSSession -computerName server
Invoke-Command -Session $s -Scriptblock {Start-ADSyncSyncCycle -PolicyType Delta}
Remove-PSSession $s
End-Script
}
#Removes any PSSessions before running
Get-PSSession | Remove-PSSession
#Find current path script is executing from
$ScriptPath = $PSScriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
$ScriptPath = $ScriptPath + "\"
#Setup Log File
$LogPath = $ScriptPath + "Logs\"
$LogFilename = "SetExchangeGUID_$((Get-Date).ToString('yyyy-MM-dd_hh-mm-ss')).log"
$LogFile = $LogPath + $LogFilename
New-Item -Path "$LogFile" -ItemType File
Write-Host "Checking Domain Admin Permissions...."
$CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$WindowsPrincipal = New-Object System.Security.Principal.WindowsPrincipal($CurrentUser)
#Make sure user is a domain admin
if(!($WindowsPrincipal.IsInRole("Domain Admins")))
{
Write-Host "You must be logged in as a Domain Admin to run this script" -ForegroundColor Red
End-Script
} else {
Write-Host "Domain Admin permissions detected, please wait....."
}
$today = Get-Date -Format yyyy-MM-dd_hh-mm-ss
$msg = "Run on " + $today + ". Run by " + $env:username
$msg | out-file $LogFile -Append
#Clear-Host
$CloudMailbox = Read-Host "Enter the identity of the cloud mailbox"
Write-Host "Connecting to EOL"
EOLExchange-Session
$SessionID = $EOLPSSession.InstanceId
Write-Host $SessionID
#Fetches EOL ExchangeGUID and trims to just the GUID
$TempCloudGUID = Get-Mailbox $CloudMailbox | Format-List ExchangeGUID | Out-String
$CloudGUID = $TempOnPremGUID.Substring(19).Trim()
#Clear-Host
Write-Host "The EOL GUID is $CloudGUID"
pause
Write-Host "Connecting to On-Prem"
OPExchange-Session
#Fetches ExchangeGUID and trims to just the GUID
$TempOnPremGUID = Get-RemoteMailbox $CloudMailbox | Format-List ExchangeGUID | Out-String
$OnPremGUID = $TempOnPremGUID.Substring(19).Trim()
#Checks if GUID is all zeros
#$ZeroGUID = "00000000-0000-0000-0000-000000000000"
#if ($OnPremGUID -eq $ZeroGUID) {
#Write-Host "The value isn't stamped on the on-premises remote mailbox. Ending script"
#End-Script
#} else {
#Write-Host $CloudMailbox "On-prem GUID is" $OnPremGUID
#}
#Clear-Host
Write-Host "EOL GUID is $CloudGUID"
Write-Host "On-prem GUID is $OnPremGUID"
if ($CloudGUID -eq $OnPremGUID) {
Write-Host "Exchange GUIDs already match, ending script."
End-Script
} else {
$confirmation = Read-Host "The GUIDs are different, would you like to set the EOL GUID to be the same as On-Prem?"
if ($confirmation -eq 'y') {
Set-RemoteMailbox $CloudMailbox -ExchangeGUID $CloudGUID
$msg = "$CloudMailbox has been changed to use $CloudGUID in EOL and On-Prem"
$msg | out-file $LogFile -Append
Write-Host "GUID for $CloudMailbox has been set. Syncing ADSyncClcye and ending script"
Get-PSSession | Remove-PSSession
SyncADConnect
}
else {End-Script}
}
No reason to do this from scratch. There are already tools/addons to do this for you.
See these:
Connect to all Office 365 Services PowerShell (Supports MFA too)
Using our All-in-One PowerShell script, you can connect to all Office
365 Services using a single cmdlet. It supports both MFA and non-MFA
account -Exchange Online -Azure AD -SharePoint Online -Skype for
Business Online -Security & Compliance Center -Teams
https://gallery.technet.microsoft.com/PowerShell-Script-to-4081ec0f/file/225256/1/ConnectO365Services.ps1
https://blog.rmilne.ca/2015/02/02/using-exchange-powershell-remoting-with-integrated-scripting-environmentise
https://jaapwesselius.com/2013/07/21/ise-remote-powershell-and-exchange-2013
Adding Exchange Shell items to PowerShell ISE
# in the Microsoft.PowerShellISE_profile.ps1 file, add the following contents:
$psISE.CurrentPowerShellTab.AddOnsMenu.SubMenus.Add(
"Connect to Exchange # Contoso", {
$ExSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri 'http://exserver.contoso.com/PowerShell/' -Authentication Kerberos
Import-PSSession $ExSession
},
"Control+Alt+1"
)
$psISE.CurrentPowerShellTab.AddOnsMenu.SubMenus.Add(
"Connect to Exchange On-Premise", {
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010
. $env:ExchangeInstallPath\bin\RemoteExchange.ps1
Connect-ExchangeServer –auto
},
"Control+Alt+2"
)
$psISE.CurrentPowerShellTab.AddOnsMenu.SubMenus.Add(
"Connect to Exchange Online", {
$o365Cred = Get-Credential
$o365Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri 'https://ps.outlook.com/powershell/' -Credential $o365Cred -Authentication Basic -AllowRedirection
Import-PSSession $o365Session
},
"Control+Alt+3"
)
As for this...
Get-PSSession | Remove-PSSession
... though it should work, I've often had this be a bit quirky, with forcing a loop.
Get-PSSession | ForEach {Remove-PSSession -Id $PSItem.Id}

Powershell - Organizer Email Address

I'm trying to retrieve the email address of the Organizer of a meeting in MS Exchange 2010, using Powershell.
(Get-Mailbox -Identity "John Doe").PrimarySmtpAddress
I get the below error
The operation couldn't be performed because object 'John Doe' couldn't be found on 'xxxxxxxxxxxxxx'
These are for meetings that have just been created, so how can the Organizer not exist?
Edit:
Here's the sequence of events:
Fetch list of calendar events from exchange within specific date range
Fetch email address of Organizer for each event <-- this is where I'm stuck
Full script:
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username,$password
$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://xxxxxxxxxxxxxxx/PowerShell -Authentication kerberos -Credential $credential
Import-PSSession -Session $session -DisableNameChecking
#Date ranges
$exportDate = Get-Date -Format d
$startTime = (Get-Date).AddDays(-1)
$endTime = (Get-Date).AddDays(+1)
$app = New-Object -ComObject Outlook.Application
$ns = $app.GetNamespace('MAPI')
$calFolder = 9
$calItems = $ns.GetDefaultFolder($calFolder).Items
$calItems.Sort("[Start]")
$calItems.IncludeRecurrences = $true
$dateRange = "[Start] >= '{0}' AND [End] <= '{1}'" -f $startTime.ToString("g"), $endTime.ToString("g")
$calExport = $calItems.Restrict($dateRange)
$exportFile = "D:\file.csv"
$calExport | select Subject, StartInStartTimeZone, EndInEndTimeZone, Duration, Organizer, RequiredAttendees, OptionalAttendees, Location | sort StartUTC -Descending | Export-Csv $exportFile
$exportData = Import-Csv $exportFile
foreach ($line in $exportData)
{
$emailAddress = $line.Organizer
$emailAddress = (Get-Mailbox -Identity $line.Organizer).PrimarySmtpAddress
$line | Add-Member -Membertype Noteproperty -Name OrganizerEmail -Value $emailAddress
[array]$csvData += $line
$emailAddress = $null
}
Remove-PSSession $session
Please assist!
Here's what I used to get the Organizers email address
Get-ADUser -Filter {SamAccountName -like "*John Doe*"}

Find the exact matched string from the variable

In the below code, $Result variable has the following information. I need to iterate below each line in $Result variable and get the <APPPOOL NAME> that is, "DefaultAppPool","Classic .NET AppPool" & ".NET v2.0 Classic" as an input to the second Invoke-Command saved in $Result2. Please advise how this can be accomplished.
$Result output:
APPPOOL "DefaultAppPool" (MgdVersion:v4.0,MgdMode:Integrated,state:Started)
APPPOOL "Classic .NET AppPool" (MgdVersion:v2.0,MgdMode:Classic,state:Started)
APPPOOL ".NET v2.0 Classic" (MgdVersion:v2.0,MgdMode:Classic,state:Started)
$Username = '<username>'
$Password = '<Password>'
$pass = ConvertTo-SecureString -AsPlainText $Password -Force
$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username, $pass
$input_file_path = "servers.txt"
$output_path = "result.txt"
foreach ($server in Get-Content $input_file_path) {
$Result = Invoke-Command -ComputerName $server -Credential $Cred -ScriptBlock {
C:\Windows\system32\inetsrv\appcmd.exe list apppools
}
$Result | Add-Content $output_path
$Result2 = Invoke-Command -ComputerName #server -Credential $Cred -ScriptBlock {
C:\Windows\system32\inetsrv\appcmd.exe list apppools <APPPOOL NAME> /text:processmodel.username
}
}

invoke-parallel in Powershell - data sharing inside script with global variable

Using invoke-parallel in Powershell, I'm trying to get a list of hosts where a certain command works vs. does not. How can I write to a global variable inside of invoke-parallel?
$creds = Get-Credential -UserName $username -Message 'Password?'
$servers = get-content .\hosts.txt
$success = #()
$failure = #()
Invoke-Parallel -InputObject $servers -throttle 20 -runspaceTimeout 30 -ImportVariables -ScriptBlock {
try
{
$result = Invoke-Command $_ -Credential $creds -Authentication "Negotiate" -ErrorAction Stop {hostname}
$success += $result
}
catch
{
$failure += $_
}
}
write-host $success
write-host $failure
Try this:
$Results = Invoke-Parallel -InputObject $servers -throttle 20 -runspaceTimeout 30 -ImportVariables -ScriptBlock {
try
{
$Output = Invoke-Command $_ -Credential $creds -Authentication "Negotiate" -ErrorAction Stop {hostname}
}
catch
{
$Output = $_
}
#($Output)
}
Final code sample for what I ended up using.
$username = "myuser"
$creds = Get-Credential -UserName $username -Message 'Password?'
$servers = get-content .\hosts.txt
$Results = Invoke-Parallel -InputObject $servers -throttle 20 -runspaceTimeout 30 -ImportVariables -ScriptBlock {
$arr = #{}
try
{
$Output = Invoke-Command $_ -Credential $creds -Authentication "Negotiate" {hostname} -ErrorAction Stop
$arr[$_] = "successful"
}
catch
{
$arr[$_] = "failed"
}
$arr
}
$Results