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
Related
I am running a PowerShell script on a server to check on other machines on the network.
I want the result of the check to be outputted in JSON format so I can send this JSON data via an api request to a web dashboard build with angular.
My set up:
Get-Request from Angular front end -> Express server -> run PowerShell script with Node-Powershell
Now I want to return the result in proper format to be used in the front end. What is the best way to accomplish this? I want to use the data to fill a material data table.
PowerShell Script status.ps1:
$Computers = #('ExampleComputer01', 'ExampleComputer02', 'ExampleComputer03', 'ExampleComputer04')
foreach ($computer in $Computers) {
gsv -cn $computer -Name ExampleProgram -ErrorAction 'SilentlyContinue'| select-object machinename, status | ConvertTo-Json
}
Api from express server.js (using Node-Powershell):
app.get('/api/psjson', (request, response) => {
ps.addCommand('./status.ps1');
ps.invoke().then(output => {
console.log(output);
response.send(JSON.parse(output));
}).catch(err => {
console.log(err);
response.send(err);
ps.dispose();
});
});
I tried using | ConvertTo-Json inside the loop but it is causing in error in node:
SyntaxError: Unexpected token { in JSON at position 55
at JSON.parse ()
Please try the following:
$Computers = #('ExampleComputer01', 'ExampleComputer02', 'ExampleComputer03', 'ExampleComputer04')
$Results = #()
foreach ($computer in $Computers) {
$Result = $null
$Result = gsv -cn $computer -Name ExampleProgram -ErrorAction 'SilentlyContinue'| select-object machinename, status
If ($Result -ne $null){
$Results += $Result
}
}
$Results | ConvertTo-Json
This builds an array of the results and then converts the array to JSON.
I think the issue you are experiencing is due to converting inside a loop and therefore the structure is incorrect.
CraftyB's answer diagnoses the problem correctly, but there's a simpler, more PowerShell-idiomatic solution that uses a single pipeline:
$computers = 'ExampleComputer01', 'ExampleComputer02', 'ExampleComputer03', 'ExampleComputer04'
gsv -cn $computers -Name Example -ErrorAction SilentlyContinue |
| Select-Object machinename, status |
ConvertTo-Json
The crucial aspect is that all input objects are passed to a single ConvertTo-Json call - see the bottom section for an explanation.
In Windows PowerShell, Get-Service (gsv) the -ComputerName (-cn) parameter directly accepts an array of computer names.
Note: In PowerShell (Core) 7+, this form of remoting is no longer supported, so there is no -ComputerName parameter; there, assuming that the target computers are set up for PowerShell remoting, you could use:
Invoke-Command -ComputerName $computers { Get-Service -Name Example -ErrorAction SilentlyContinue }
As for what you tried:
If you call ConvertTo-Json inside a loop, per input object, you will implicitly output multiple, independent JSON strings instead of a single JSON string representing the input objects as a JSON array:
Given the following sample input objects:
$objects = [pscustomobject] #{ foo=1 }, [pscustomobject] #{ foo=2 }
It is the difference between:
# !! BROKEN: *multiple* ConvertTo-Json calls.
# !! When the result is stringified, you get a space-separated list of the
# !! individual JSON strings, which is NOT valid JSON.
PS> #"
$(
foreach ($object in $objects) { $object | ConvertTo-Json -Compress }
)
"#
{"foo":1} {"foo":2} # NOT valid JSON.
and:
# OK: *Single* ConvertTo-Json call, which outputs a valid JSON array.
PS> $objects | ConvertTo-Json -Compress
[{"foo":1},{"foo":2}] # OK: valid JSON array.
I am posting this question in the style of question and answer.
I recently ran into an issue where I could not add Reply Urls to an Azure AD App Registration, which was registered in a B2C Tenant.
I had been happily updating the Reply Urls using automation using the az ad app update --id $appIdentifier --add replyUrls "https://<a-valid-dns-zone-record> command.
However, once you use your App Registration from a B2C tenant to perform the first login, the command above stops working. You CAN carry on using the portal to add new Reply Urls, but that isnt any good for automation!
I had googled and searched stackoverflow for answers but found none. I raised a Premier Support ticket with Microsoft, and a Github issue on the Azure CLI tool.
I got a workaournd for the problem from my Github Issue
Which I will detail in my answer
The solution is to use the az rest command and use it to call Graph API directly. Here is a PowerShell script I use in an Azure DevOps yaml pipeline.
[CmdletBinding()]
Param(
[Parameter(Mandatory)]
[String]$servicePrincipalId,
[Parameter(Mandatory)]
[String]$servicePrincipalPassword,
[Parameter(Mandatory)]
[String]$servicePrincipalTenantId,
[Parameter(Mandatory)]
[String]$newReplyUrl,
[Parameter(Mandatory)]
[String]$appRegIdentifier
)
Write-Host "Logging in as B2C Tenant Service Principal"
$null = az login --service-principal -u $servicePrincipalId -p $servicePrincipalPassword -t $servicePrincipalTenantId --allow-no-subscriptions
# Load some JSON/Text file helpers...
Import-Module $PSScriptRoot\Json-Helpers.psm1
Write-Host "Adding $newReplyUrl if required..."
$appRegObjectId = az ad app show --id $appRegIdentifier --query 'objectId'
$graphAppRegUri = "https://graph.microsoft.com/v1.0/applications/$appRegObjectId"
$appJson = az rest --method GET --uri $graphAppRegUri
$app = $appJson | ConvertFrom-Json
if ($app.web.redirectUris.Contains($newReplyUrl)) {
# ReplyUrl exists, no-op
Write-Host "Reply Url already exists, no action required..."
Exit 0
}
$patchApp = #{ web = #{ redirectUris = #() } }
ForEach ($url in $app.web.redirectUris) {
$patchApp["web"].redirectUris += $url
}
$patchApp["web"].redirectUris += $newReplyUrl
$tempFile = ".\patchAppTemp-$([System.Guid]::NewGuid()).json"
try {
$patchApp | ConvertTo-Json -Depth 5 | Format-Json -Minify | Set-Content -Path $tempFile -Encoding UTF8 -NoNewline
# Update `redirectUris` for `web` property
Write-Host "Calling Graph API to update Reply Urls"
az rest --method patch --uri $graphAppRegUri --headers "Content-Type=application/json" --body=#$tempFile
Write-Host "Successfully added $newReplyUrl to App Registration $appRegIdentifier"
}
finally {
if (Test-Path $tempFile) {
Remove-Item -Path $tempFile
}
}
And for completeness, here is the Json-Helpers.psm1 PowerShell module:
function Get-Encoding
{
param
(
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
[Alias('FullName')]
[string]
$Path
)
process
{
$bom = New-Object -TypeName System.Byte[](4)
$file = New-Object System.IO.FileStream($Path, 'Open', 'Read')
$null = $file.Read($bom,0,4)
$file.Close()
$file.Dispose()
$enc = 'Ascii'
if ($bom[0] -eq 0x2b -and $bom[1] -eq 0x2f -and $bom[2] -eq 0x76)
{ $enc = 'Utf7' }
if ($bom[0] -eq 0xff -and $bom[1] -eq 0xfe)
{ $enc = 'Unicode' }
if ($bom[0] -eq 0xfe -and $bom[1] -eq 0xff)
{ $enc = 'Bigendianunicode' }
if ($bom[0] -eq 0x00 -and $bom[1] -eq 0x00 -and $bom[2] -eq 0xfe -and $bom[3] -eq 0xff)
{ $enc = 'Utf32'}
if ($bom[0] -eq 0xef -and $bom[1] -eq 0xbb -and $bom[2] -eq 0xbf)
{ $enc = 'Utf8'}
[PSCustomObject]#{
Encoding = $enc
Path = $Path
}
}
}
function Format-Json {
<#
.SYNOPSIS
Prettifies JSON output.
.DESCRIPTION
Reformats a JSON string so the output looks better than what ConvertTo-Json outputs.
.PARAMETER Json
Required: [string] The JSON text to prettify.
.PARAMETER Minify
Optional: Returns the json string compressed.
.PARAMETER Indentation
Optional: The number of spaces (1..1024) to use for indentation. Defaults to 4.
.PARAMETER AsArray
Optional: If set, the output will be in the form of a string array, otherwise a single string is output.
.EXAMPLE
$json | ConvertTo-Json | Format-Json -Indentation 2
#>
[CmdletBinding(DefaultParameterSetName = 'Prettify')]
Param(
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[string]$Json,
[Parameter(ParameterSetName = 'Minify')]
[switch]$Minify,
[Parameter(ParameterSetName = 'Prettify')]
[ValidateRange(1, 1024)]
[int]$Indentation = 4,
[Parameter(ParameterSetName = 'Prettify')]
[switch]$AsArray
)
if ($PSCmdlet.ParameterSetName -eq 'Minify') {
return ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100 -Compress
}
# If the input JSON text has been created with ConvertTo-Json -Compress
# then we first need to reconvert it without compression
if ($Json -notmatch '\r?\n') {
$Json = ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100
}
$indent = 0
$regexUnlessQuoted = '(?=([^"]*"[^"]*")*[^"]*$)'
$result = $Json -split '\r?\n' |
ForEach-Object {
# If the line contains a ] or } character,
# we need to decrement the indentation level unless it is inside quotes.
if ($_ -match "[}\]]$regexUnlessQuoted") {
$indent = [Math]::Max($indent - $Indentation, 0)
}
# Replace all colon-space combinations by ": " unless it is inside quotes.
$line = (' ' * $indent) + ($_.TrimStart() -replace ":\s+$regexUnlessQuoted", ': ')
# If the line contains a [ or { character,
# we need to increment the indentation level unless it is inside quotes.
if ($_ -match "[\{\[]$regexUnlessQuoted") {
$indent += $Indentation
}
$line
}
if ($AsArray) { return $result }
return $result -Join [Environment]::NewLine
}
The key line is
az rest --method patch --uri $graphAppRegUri --headers "Content-Type=application/json" --body=#$tempFile
Which calls the Graph API, passing the Json content of your dynamically created temporary file. One small downside to doing it this way is that you take the current URLs, add your new one, and patch the whole list, rather than just adding the one you wanted. As the list grows, the content of the patch could get large too.
But as things stand today, this is the best way to get around the problem that I found.
While Ian had a very good answer and would not have been able to get started without it, it also seems bit too complex. With patch command you do not need to write all the json gotten from the GET call, but only the part you want to update.
$myTestServiceUri = "https://my-test-url.com"
$myObjectId = "<OBJECT_ID_GUID_HERE>"
$graphAppRegUri = "https://graph.microsoft.com/v1.0/applications/$myObjectId"
az rest --method GET --uri $graphAppRegUri
$newUriArr = ((az rest --method GET --uri $graphAppRegUri | ConvertFrom-Json).spa.redirectUris + $myTestServiceUri)
(#{spa=#{redirectUris=$newUriArr}} | ConvertTo-Json) | Set-Content -Encoding UTF8 -NoNewlin -Path tmp-redirectUris.json
az rest --method PATCH --uri $graphAppRegUri --headers "Content-Type=application/json" --body=#tmp-redirectUris.json
Also good to do login before running this to test locally (had to use no subscription as Azure AD B2C tenant has got no subscription at least in my case):
az login --tenant <TENANT_NAME_HERE>.onmicrosoft.com --allow-no-subscriptions
When logging in with automated setup, one needs to use Azure B2C application configured with correct access rights and client secret (see instructions from Azure B2C docs):
az login --service-principal --username APP_ID --password SP1_PASSWORD --tenant TENANT_ID
From the Microsoft Graph REST API docs one can also see the required permissions for the command and can configure those to B2C application. So you will need Application.ReadWrite.All if you are going to manage all your Applications with the same "service principal". If only this one application, use Application.ReadWrite.OwnedBy.
I'm actually some automation for my ADF. As a part of that, I'm trying to delete all the ADF V2 pipelines. The problem is my pipelines having many references with different pipelines itself.
$ADFPipeline = Get-AzDataFactoryV2Pipeline -DataFactoryName $(datafactory-name) -ResourceGroupName $(rg)
$ADFPipeline | ForEach-Object { Remove-AzDataFactoryV2Pipeline -ResourceGroupName $(rg) -DataFactoryName $(datafactory-name) -Name $_.name -Force }
And most of the time I get the error like
The document cannot be deleted since it is referenced by "blabla"
I understand the error that it saying some references and cannot be deleted. However, when I tried the same deletion in the azure portal, irrespective of the reference I can able to delete. So I want to find a way that whether it possible to tell that Powershell even though it's having a reference delete it forcefully
Any other inputs much appreciated!
I run into the same issue, found out that it's rather complicated to build the whole dependency graph out of the pipeline's Activities property.
As a working solution (powershell):
function Remove-Pipelines {
param (
[Parameter(Mandatory=$true)]
[AllowEmptyCollection()]
[AllowNull()]
[System.Collections.ArrayList]$pipelines
)
if($pipelines.Count -gt 0) {
[System.Collections.ArrayList]$plsToProcess = New-Object System.Collections.ArrayList($null)
foreach ($pipeline in $pipelines) {
try {
$removeAzDFCommand = "Remove-AzDataFactoryV2Pipeline -dataFactoryName '$DataFactoryName' -resourceGroupName '$ResourceGroupName' -Name '$($pipeline.Name)' -Force -ErrorAction Stop"
Write-Host $removeAzDFCommand
Invoke-Expression $removeAzDFCommand
}
catch {
if ($_ -match '.*The document cannot be deleted since it is referenced by.*') {
Write-Host $_
$plsToProcess.Add($pipeline)
} else {
throw $_
}
}
}
Remove-Pipelines $plsToProcess
}
}
Here is the complete solution for clearing the whole DF: "trigger","pipeline","dataflow","dataset","linkedService"
Param(
[Parameter(Mandatory=$true)][string] $ResourceGroupName,
[Parameter(Mandatory=$true)][string] $DataFactoryName
)
$artfTypes = "trigger","pipeline","dataflow","dataset","linkedService"
function Remove-Artifacts {
param (
[Parameter(Mandatory=$true)][AllowEmptyCollection()][AllowNull()][System.Collections.ArrayList]$artifacts,
[Parameter(Mandatory=$true)][string]$artfType
)
if($artifacts.Count -gt 0) {
[System.Collections.ArrayList]$artToProcess = New-Object System.Collections.ArrayList($null)
foreach ($artifact in $artifacts) {
try {
$removeAzDFCommand = "Remove-AzDataFactoryV2$($artfType) -dataFactoryName '$DataFactoryName' -resourceGroupName '$ResourceGroupName' -Name '$($artifact.Name)' -Force -ErrorAction Stop"
Write-Host $removeAzDFCommand
Invoke-Expression $removeAzDFCommand
}
catch {
if ($_ -match '.*The document cannot be deleted since it is referenced by.*') {
Write-Host $_
$artToProcess.Add($artifact)
} else {
throw $_
}
}
}
Remove-Artifacts $artToProcess $artfType
}
}
foreach ($artfType in $artfTypes) {
$getAzDFCommand = "Get-AzDataFactoryV2$($artfType) -dataFactoryName '$DataFactoryName' -resourceGroupName '$ResourceGroupName'"
Write-Output $getAzDFCommand
$artifacts = Invoke-Expression $getAzDFCommand
Write-Output $artifacts.Name
Remove-Artifacts $artifacts $artfType
}
The same approach can be adapted for "Set-AzDataFactoryV2Pipeline" command as well.
It worth to mention that along with dependencies tracking, Remove/Set artifact's sequence should be right (because of cross artifacts' dependencies).
For Set - "linkedService","dataset","dataflow","pipeline","trigger"
For Remove - "trigger","pipeline","dataflow","dataset","linkedService"
Hello and thank you for the question. According to the Remove-AzDataFactoryV2Pipeline doc, the -Force flag simply skips the confirmation prompt. It does not actually 'Force' the deletion in spite of errors.
Since you are already doing automation, might I suggest leveraging the error message to recursively attempt to delete the referencing pipeline. $error[0] gets the most recent error.
(Pseudocode)
try_recurse_delete( pipeline_name )
do_delete(pipeline_name)
if not $error[0].contains("referenced by " + pipeline_name)
then return true
else
try_recurse_delete( get_refrencer_name($error[0]) )
Given that pipeline dependencies can be a many-to-many relationship, subsequent pipelines in your for-each loop might already be deleted by the recursion. You will have to adapt your code to react to 'pipeline not found' type errors.
I want to monitor who made a change in rbac assignment, I created powershell script for collection data from Azure Activity Log. I used below piece of code. Using this solution I am able to get items like:
caller - user who made a role assignment change,
timestamp,
Resource name - on this resource assignment change has been provided,
action type - write or delete
In Activity Log panel in Azure portal, in Summary portal (Message: shared with "user info"), I can see name of a user who has been granted permissions/assignment to the resource, but using my powershell script I am not able to catch this information, is there any method to get this info?
Get-AzureRmLog -StartTime (Get-Date).AddDays(-7) |
Where-Object {$_.Authorization.Action -like
'Microsoft.Authorization/roleAssignments/*'} |
Select-Object #{N="Caller";E={$_.Caller}},
#{N="Resource";E={$_.Authorization.Scope}},
#{N="Action";E={Split-Path $_.Authorization.action -leaf}},
EventTimestamp
script output:
Caller : username#xxx.com
Resource :/subscriptions/xxxx/resourceGroups/Powershell/providers/Microsoft.Compute/virtualMachines/xx/providers/Microsoft.Authorization/roleAssignments/xxxx
Action : write
EventTimestamp : 8/29/2019 10:12:31 AM
Your requirement of fetching the user name to whom the RBAC role is assigned is currently not supported using Az PowerShell cmdlet Get-AzLog or Get-AzureRmLog.
However, we can accomplish your requirement by leveraging Azure REST API for Activity Logs - List and Az PowerShell cmdlet Get-AzureADUser.
In this way as we are depending on Azure REST API for Activity Logs - List (but looks like you want PowerShell way of accomplishing the requirement) so call the REST API in PowerShell as something shown below.
$request = "https://management.azure.com/subscriptions/{subscriptionId}/providers/microsoft.insights/eventtypes/management/values?api-version=2015-04-01&`$filter={$filter}"
$auth = "eyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$authHeader = #{
'Content-Type'='application/json'
'Accept'='application/json'
'Authorization'= "Bearer $auth"
}
$Output = Invoke-RestMethod -Uri $request -Headers $authHeader -Method GET -Body $Body
$ActivityLogsFinalOutput = $Output.Value
Develop your PowerShell code to get "PrincipalId" (which is under "properties") from the output of your Azure REST API for Activity Logs - List call. The fetched "PrincipalId" is the ObjectID of the user whom you want to get ultimately.
Now leverage Az PowerShell cmdlet Get-AzureADUser and have your command something like shown below.
(Get-AzureADUser -ObjectID "<PrincipalID>").DisplayName
Hope this helps!! Cheers!!
UPDATE:
Please find PowerShell way of fetching auth token (i.e., $auth) that needs to be used in above REST API call.
$ClientID = "<ClientID>" #ApplicationID
$ClientSecret = "<ClientSecret>" #key from Application
$tennantid = "<TennantID>"
$TokenEndpoint = {https://login.windows.net/{0}/oauth2/token} -f $tennantid
$ARMResource = "https://management.core.windows.net/";
$Body1 = #{
'resource'= $ARMResource
'client_id' = $ClientID
'grant_type' = 'client_credentials'
'client_secret' = $ClientSecret
}
$params = #{
ContentType = 'application/x-www-form-urlencoded'
Headers = #{'accept'='application/json'}
Body = $Body1
Method = 'Post'
URI = $TokenEndpoint
}
$token = Invoke-RestMethod #params
$token | select access_token, #{L='Expires';E={[timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddSeconds($_.expires_on))}} | fl *
I see this new way as well but I didn't get chance to test this out. If interested, you may alternatively try this or go with above approach.
UPDATE2:
$ActivityLogsFinalOutput| %{
if(($_.properties.responseBody) -like "*principalId*"){
$SplittedPrincipalID = $_.properties.responseBody -split "PrincipalID"
$SplittedComma = $SplittedPrincipalID[1] -split ","
$SplittedDoubleQuote = $SplittedComma[0] -split "`""
$PrincipalID = $SplittedDoubleQuote[2]
#Continue code for getting Azure AD User using above fetched $PrincipalID
#...
#...
}
}
Does this work for you?
Get-AzureRmLog -StartTime (Get-Date).AddDays(-7) |
Where-Object {$_.Authorization.Action -like 'Microsoft.Authorization/roleAssignments/*'} |
Select-Object #{N="Caller";E={$_.Caller}},
#{N="Resource";E={$_.Authorization.Scope}},
#{N="Action";E={Split-Path $_.Authorization.action -leaf}},
#{N="Name";E={$_.Claims.Content.name}},
EventTimestamp
My output:
Caller : username#domain.com
Resource : /subscriptions/xxxx/resourceGroups/xxxx/providers/Microsoft.Authorization/roleAssignments/xxxx
Action : write
Name : John Doe
EventTimestamp : 30.08.2019 12.05.52
NB: I used Get-AzLog. Not sure if there is any difference between Get-AzLog and Get-AzureRmLog.
Fairly certain this wouldn't be exposed with this cmdlet. I dont even see this information in the Role Assignments. So not sure what do you mean exactly.
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
}