Validating PowerShell PSCredential - security

Let's say I have a PSCrendential object in PowerShell that I created using Get-Credential.
How can I validate the input against Active Directory ?
By now I found this way, but I feel it's a bit ugly :
[void][System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.AccountManagement")
function Validate-Credentials([System.Management.Automation.PSCredential]$credentials)
{
$pctx = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Domain, "domain")
$nc = $credentials.GetNetworkCredential()
return $pctx.ValidateCredentials($nc.UserName, $nc.Password)
}
$credentials = Get-Credential
Validate-Credentials $credentials
[Edit, two years later] For future readers, please note that Test-Credential or Test-PSCredential are better names, because Validate is not a valid powershell verb (see Get-Verb)

I believe using System.DirectoryServices.AccountManagement is the less ugly way:
This is using ADSI (more ugly?):
$cred = Get-Credential #Read credentials
$username = $cred.username
$password = $cred.GetNetworkCredential().password
# Get current domain using logged-on user's credentials
$CurrentDomain = "LDAP://" + ([ADSI]"").distinguishedName
$domain = New-Object System.DirectoryServices.DirectoryEntry($CurrentDomain,$UserName,$Password)
if ($domain.name -eq $null)
{
write-host "Authentication failed - please verify your username and password."
exit #terminate the script.
}
else
{
write-host "Successfully authenticated with domain $domain.name"
}

I was having a similar issue with an installer and required to verify the service account details supplied. I wanted to avoid using the AD module in Powershell as I wasn't 100% this would be installed on the machine running the script.
I did the test using the below, it is slightly dirty but it does work.
try{
start-process -Credential $c -FilePath ping -WindowStyle Hidden
} catch {
write-error $_.Exception.Message
break
}

Related

Powershell Function -WhatIf for cmdlet without the -WhatIf support?

For these cmdlets below, I try to create the function with the [CmdletBinding(SupportsShouldProcess)] line, but not sure that it will work.
Using: https://learn.microsoft.com/en-us/powershell/module/msonline/set-msoluserlicense?view=azureadps-1.0
How can the script be modified to support the -WhatIf parameter?
function Remove-License {
[CmdletBinding(SupportsShouldProcess)]
param ([String] $UserPrincipalName )
$AssignedLicense = (Get-MsolUser -UserPrincipalName $UserPrincipalName).licenses.AccountSkuId
$AssignedLicense |
ForEach-Object {
Write-Host "Removing $($UserPrincipalName) License $($AssignedLicense)..." -ForegroundColor Red
Set-MsolUserLicense -UserPrincipalName $upn -RemoveLicenses $_ -Verbose
}
}
Remove-License -UserPrincipalName 'User.Name#domain.com' -WhatIf
You can use $PSCmdlet.ShouldProcess
if($PSCmdlet.ShouldProcess("Some text to display")){
Set-MsolUserLicense -UserPrincipalName $upn -RemoveLicenses $_ -Verbose
}
MS Docs:
https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-shouldprocess?view=powershell-7.2
To complement guiwhatsthat helpful answer, if you want your function to support Common Parameter including -WhatIf as well as -Confirm, $PSCmdlet.ShouldProcess is how you do it.
If instead you want Set-MsolUserLicense to support it, you would need to create a proxy command / proxy function around this cmdlet to extend it's functionality.
These blogs demonstrate how to do it:
https://devblogs.microsoft.com/scripting/proxy-functions-spice-up-your-powershell-core-cmdlets/
https://devblogs.microsoft.com/powershell/extending-andor-modifing-commands-with-proxies/
To address some issues on your code, you should note that -RemoveLicenses takes string[] as input, this means you can pass the whole array of licenses to remove as argument.
You could use Write-Verbose instead of Write-Host to display information about what the function is doing (since your function already supports -Verbose, this would be logical). Also, -Verbose is being used always activated on Set-MsolUserLicense which can be confusing if someone else using your function does not want to see verbose messages (this is addressed on the example below).
You can also use ConfirmImpact set to High, this way the function will always ask for confirmation before processing any license removal assuming -WhatIf was not being used. -Confirm:$false becomes the alternative to avoid such confirmation messages.
function Remove-License {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
param(
[Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
[string] $UserPrincipalName
)
process {
if($PSCmdlet.ShouldProcess([string] $UserPrincipalName, 'Remove License')) {
$licenses = (Get-MsolUser -UserPrincipalName $UserPrincipalName).licenses.AccountSkuId
$param = #{
UserPrincipalName = $UserPrincipalName
RemoveLicenses = $licenses
Verbose = $PSBoundParameters['Verbose']
}
Set-MsolUserLicense #param
}
}
}
Now the function supports pipeline processing, you can process multiple users by piping it into other cmdlets, i.e.:
Get-MsolUser -EnabledFilter DisabledOnly -MaxResults 5 | Remove-License -WhatIf

Get-PnPTenantSite : Attempted to perform an unauthorized operation

Currently we get an access token and then pass this token to PowerShell script to loop across all ODFB personal sites.
$url = "https://XXXXX-admin.sharepoint.com"
$conn = Connect-PnPOnline -Url $url -AccessToken $access_token -ReturnConnection
$sitecollections = Get-PnPTenantSite -IncludeOneDriveSites:$true -Filter "Url -like '-my.sharepoint.com/personal/'" -Connection $conn | Select-Object -ExpandProperty Url
foreach ($site in $sitecollections)
{
....
}
It worked successfully for years until it was broken a while ago.
I tried different versions of PnP PowerShell:
PnP version
Error
SharePointPnPPowerShellOnline 3.21.2005.2 (currently used)
Get-PnPTenantSite : Attempted to perform an unauthorized operation.
SharePointPnPPowerShellOnline 3.29.2101.0
Get-PnPTenantSite : The current connection holds no SharePoint context.
PnP.PowerShell 1.10.28
Get-PnPTenantSite : Attempted to perform an unauthorized operation.
If I change script to use an user/password instead the access token, the script works without problems:
$url = "https://XXXXX-admin.sharepoint.com"
$User = "admin#mydomain.com"
$PWord = ConvertTo-SecureString -String "Password" -AsPlainText -Force
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $PWord
$conn = Connect-PnPOnline -Url $url -Credentials $Credential -ReturnConnection
$sitecollections = Get-PnPTenantSite -IncludeOneDriveSites:$true -Filter "Url -like '-my.sharepoint.com/personal/'" -Connection $conn | Select-Object -ExpandProperty Url
foreach ($site in $sitecollections)
{
....
}
So the error happens when the script connects to SP Online using an access token.
Perhaps the some things were changed. But what exactly? Have some scope to be added when an access token is requested?
Or have some new permissions to be added for the application in Azure AD?
Update:
Modified the script (added Write-Output "Connection is:" $conn | fl) to provide more details about connection and got the difference in ConnectionType property when SharePointPnPPowerShellOnline 3.21.2005.2 is used:
When an access token is used (and the script doesn't work properly), ConnectionType : O365
When an access token is used (and the script works fine), ConnectionType : TenantAdmin

How can I established credentials in PowerShell script running in an Azure Automation Account

I'm trying to get a simple Powershell script working in Azure Automation Accounts; I've tested the script in VS Code and it works fine; the issue is in the Credentials; following this page: https://learn.microsoft.com/en-us/azure/automation/shared-resources/credentials?tabs=azure-powershell, I'm using the following code
# Connect to Azure
$myCredential = Get-AutomationPSCredential -Name 'XXX'
$myUserName = $myCredential.UserName
$mySecurePassword = $myCredential.Password
$myPSCredential = New-Object System.Management.Automation.PSCredential ($myUserName, $mySecurePassword)
Connect-AzureAD -Credential $myPSCredential
The only different to my "local" script is the first of the above lines which uses a local file
$myCredential = Import-CliXml -Path 'C:\Users\<me>\Desktop\credentials.xml'
But it doesn't work; diagnostics seem to be poor in Automation Accounts, but I'm 99% sure is related to credentials; perhaps it's forcing MFA, but that's not happening locally ... any suggestions appreciated
If you are using the older RunAs account then you can use the following:
$connectionName = "connectionName"
try {
# Get the connection "AzureRunAsConnection" You can't use the Az module version for reasons.
$servicePrincipalConnection = Get-AutomationConnection -Name $connectionName
"Logging in to Azure..."
$connectAzAccountSplat = #{
ServicePrincipal = $true
TenantId = $servicePrincipalConnection.TenantId
ApplicationId = $servicePrincipalConnection.ApplicationId
CertificateThumbprint = $servicePrincipalConnection.CertificateThumbprint
}
Connect-AzAccount #connectAzAccountSplat -ErrorAction Stop | Out-Null
}
catch {
if (!$servicePrincipalConnection) {
$ErrorMessage = "Connection $connectionName not found."
throw $ErrorMessage
}
else {
Write-Error -Message $_.Exception
throw $_.Exception
}
}
Automation Accounts have recently been updated to use system assigned identities however. You can find the docs of that here:
https://learn.microsoft.com/en-us/azure/automation/enable-managed-identity-for-automation

Make Azure Authentication Run Silently without Password Prompt

I have a PowerShell script that connects to Azure, then downloads data. The script runs great with human interaction, but I'm trying to run it silently as a scheduled task. Currently, every time the script runs, it prompts for user credentials. I change 'Always' to 'Never' and it doesn't seem to store the credentials for any length of time.
$clientId = "<CLIENTIDHERE>" # PowerShell clientId
$redirectUri = "<REDIRECTURIHERE>"
$MSGraphURI = "https://graph.microsoft.com"
$authority = "https://login.microsoftonline.com/$tenantId"
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
$authResult = $authContext.AcquireToken($MSGraphURI, $clientId, $redirectUri, "Always")
$token = $authResult.AccessToken
Ideally the credentials would be passed through based on the credentials running in the scheduled task. If that isn't an option, at least I'm hoping to put the username and password in the script and have the script send those credentials to authenticate. How does one authenticate silently to Azure?
You could check the script shared by Bogdan Gavril from this thread .
#Require -Version 5.0
using namespace Microsoft.IdentityModel.Clients.ActiveDirectory
$adalDll = [Reflection.Assembly]::LoadFile("<path_to>\Microsoft.IdentityModel.Clients.ActiveDirectory.dll")
$ADAuthorityURL = "https://login.windows.net/common/oauth2/authorize/"
$resourceURL = "https://analysis.windows.net/powerbi/api"
$AADuserName = "foo"
$AADpassword = "bar"
Write-Host "Retrieving the AAD Credentials...";
$credential = New-Object UserPasswordCredential($AADuserName, $AADpassword);
$authenticationContext = New-Object AuthenticationContext($ADAuthorityURL);
$authenticationResult = [AuthenticationContextIntegratedAuthExtensions]::AcquireTokenAsync($authenticationContext, $resourceURL, $AADClientID, $credential).Result;
$ResultAAD = $authenticationResult.AccessToken;
I was able to figure this out. The initial authentication code I presented used an Azure-specific pop-up window to grab your credentials. Using the following link [1] I converted the code to the PowerShell Get-Credential method instead. From there, I used the information in this link [2] (Example 7) to configure the Get-Credential method to pull from plain text instead of a pop-up Window.
Now plain text passwords isn't ideal, but for our needs, it was good enough.
$clientId = "<CLIENTIDHERE>" # PowerShell clientId
$redirectUri = "REDIRECTURIHERE"
$MSGraphURI = "https://graph.microsoft.com"
$authority = "https://login.microsoftonline.com/$tenantId"
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
$User = "<USERNAMEHERE>"
$PWord = ConvertTo-SecureString -String "<PASSWORDHERE>" -AsPlainText -Force
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $PWord
$AADCredential = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserCredential" -ArgumentList $credential.UserName,$credential.Password
$authResult = $authContext.AcquireToken($MSGraphURI, $clientId, $AADCredential)
$token = $authResult.AccessToken
[1] https://blogs.technet.microsoft.com/cloudlojik/2017/09/05/using-powershell-to-connect-to-microsoft-graph-api/
[2] https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/get-credential?view=powershell-6

how to operate On List of IIS Application Pools On Remote Server Using Powershell?

I am trying to build powershell program which would:
connect to the remote server
Show number of active IIS app pools on the active server
based on the selection (1,2,3,4,....n etc) it would reset app pool
Can you please give me some tips?
Give this a try:
[Reflection.Assembly]::LoadWithPartialName('Microsoft.Web.Administration')
$sm = [Microsoft.Web.Administration.ServerManager]::OpenRemote('server1')
$sm.ApplicationPools['AppPoolName'].Recycle()
Building upon the answers already given, try the following. It uses powershell remoting, specifically Invoke-Command so you need to familiarise yourself with that.
[cmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")]
param
(
[parameter(Mandatory=$true,ValueFromPipeline=$true)]
[string]$ComputerName,
[parameter(Mandatory=$false)]
[System.Management.Automation.PSCredential]$Credential
)
begin
{
if (!($Credential))
{
# Prompt for credentials if not passed in
$Credential = get-credential
}
$scriptBlock = {
Import-Module WebAdministration
# Get all running app pools
$applicationPools = Get-ChildItem IIS:\AppPools | ? {$_.state -eq "Started"}
$i = 0
# Display a basic menu
Write-Host "`nApplication Pools`n"
$applicationPools | % {
"[{0}]`t{1}" -f $i, $($applicationPools[$i].Name)
$i++
}
# Get their choice
$response = Read-Host -Prompt "`nSelect Application Pool to recycle"
# Grab the associated object, which will be null
# if an out of range choice was entered
$appPool = $applicationPools[$response]
if ($appPool)
{
"Recycling '{0}'" -f $appPool.name
$appPool.recycle()
}
}
}
process
{
Invoke-Command -ComputerName $computerName -Credential $credential -ScriptBlock $scriptBlock
}
I cannot help with existing code, but which some links
Check out remote powershell sessions here
Check out the Web Server (IIS) Administration Cmdlets in Windows PowerShell, specialy the Get-WebApplication and Get-WebAppPoolState
If reset means stop, then you could take a look on Stop-WebAppPool

Resources