I'm trying to create a DSC script that can be run locally on a machine that is to be a Read Only Domain Controller. The xActiveDirectory DSC resource doesn't provide for creating an RODC so I have to use a script resource and use Install-ADDSDomainController.
My problem arises when I have to provide the Safe Mode Administrator Password. The parameter will only accept a SecureString, however I'm having trouble passing through the secure string to the DSC configuration. I can pass through a PSCredential object for the Credential parameter but the Safe Mode parameter won't accept it so I need a separate variable. I am encrypting the credentials with a self signed cert which seems to be working ok at this point.
My DSC code, there are a couple of commented out lines at the bottom where I tested alternate ways of creating the secure string non of which worked:
get-childitem cert:\localmachine\my | where-object {$_.Subject -like "*CN=DscEncryptionCert*"} | remove-item
$cert = New-SelfSignedCertificate -Type DocumentEncryptionCertLegacyCsp -DnsName 'DscEncryptionCert' -HashAlgorithm SHA256
$cert | Export-Certificate -FilePath "c:\RODC\DscPublicKey.cer" -Force
$thumbprint = (get-childitem cert:\localmachine\my | where-object {$_.Subject -like "*CN=DscEncryptionCert*"}).Thumbprint
$ConfigData= #{
AllNodes = #(
#{
NodeName = "localhost"
CertificateFile = "C:\RODC\localhost.cer"
Thumbprint = $thumbprint
};
);
}
configuration RODC
{
param(
[Parameter()]$DomainName,
[Parameter()]$ReplicationSourceDC,
[Parameter()]$SiteName,
[Parameter()]$Thumbprint,
[PSCredential]$PSCredential = $PSCredential,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[System.Security.SecureString]$safemodepassword = $safemodepassword
)
Import-DscResource -module 'PSDesiredStateConfiguration'
Node localhost
{
LocalConfigurationManager
{
CertificateId = $Thumbprint
}
WindowsFeature ADDSInstall
{
Ensure = 'Present'
Name = 'AD-Domain-Services'
IncludeAllSubFeature = $true
}
script installRODC
{
DependsOn = '[WindowsFeature]ADDSInstall'
SetScript =
{
Import-Module ADDSDeployment
Install-ADDSDomainController `
-AllowPasswordReplicationAccountName #("test\Allowed RODC Password Replication Group") `
-NoGlobalCatalog:$false `
-Credential:$PSCredential `
-CriticalReplicationOnly:$false `
-DenyPasswordReplicationAccountName #("BUILTIN\Administrators", "BUILTIN\Server Operators", "BUILTIN\Backup Operators", "BUILTIN\Account Operators", "test\Denied RODC Password Replication Group") `
-DomainName:$using:DomainName `
-InstallDns:$true `
-NoRebootOnCompletion:$false `
-ReadOnlyReplica:$true `
-ReplicationSourceDC:$using:ReplicationSourceDC `
-SiteName $using:SiteName `
-Force:$true `
-SafeModeAdministratorPassword:$safemodepassword
}
TestScript =
{
if((get-wmiobject win32_computersystem).domainrole -eq 4){$true}else{$false}
}
GetScript =
{
Return #{result = (get-wmiobject win32_computersystem).domainrole}
}
}
}
}
$PSCredential = Get-Credential
$safemodepassword = Read-Host -assecurestring "Please enter the Safe Mode Administrator password"
#$safemodepassword = ConvertTo-SecureString "P#55word" -AsPlainText -Force
#$safemodepassword = New-Object System.Management.Automation.PSCredential ("Administrator", $password)
RODC -DomainName test.local -ReplicationSourceDC DC1.test.local -Sitename Site11 -PSCredential $PSCredential -safemodepassword $safemodepassword
Set-DscLocalConfigurationManager -path .\RODC -Verbose -Force
Start-DscConfiguration -path .\RODC -Verbose -force
A simple test I wrote to check if the script code itself is working, which it is:
$PSCredential = Get-Credential
$safemodepassword = Read-Host -assecurestring "Please enter the Safe Mode Administrator password"
$DomainName = "test.local"
$ReplicationSourceDC = "DC1.test.local"
$Sitename = "Site11"
Install-ADDSDomainController `
-AllowPasswordReplicationAccountName #("test\Allowed RODC Password Replication Group") `
-NoGlobalCatalog:$false `
-Credential:$PSCredential `
-CriticalReplicationOnly:$false `
-DenyPasswordReplicationAccountName #("BUILTIN\Administrators", "BUILTIN\Server Operators", "BUILTIN\Backup Operators", "BUILTIN\Account Operators", "test\Denied RODC Password Replication Group") `
-DomainName:$DomainName `
-InstallDns:$true `
-NoRebootOnCompletion:$false `
-ReadOnlyReplica:$true `
-ReplicationSourceDC:$ReplicationSourceDC `
-SiteName $SiteName `
-Force:$true `
-SafeModeAdministratorPassword:$safemodepassword
The main error I get is:
PowerShell DSC resource MSFT_ScriptResource failed to execute
Set-TargetResource functionality with error message: Cannot bind
parameter 'SafeModeAdministratorPassword' to the target. Exception
setting "SafeModeAdministratorPassword":
"SafeModeAdministratorPassword cannot be null."
Is it NULL because it's not being passed through correctly? If I print out the value of the variable it tells me there is a secure string present but that doesn't seem to be the case in the actual DSC configuration itself.
If I change -SafeModeAdministratorPassword:$safemodepassword to include $using as I have with some of the other variables I get the error:
PowerShell DSC resource MSFT_ScriptResource failed to execute
Set-TargetResource functionality with error message: Exception calling
"Deserialize" with "1" argument(s): "The system cannot find the path
specified.
I'm not sure where I can go from here. Any help would be appreciated. Thanks.
I think it's not possible to pass SecureString inside Script block.
It's encrypted by key which exists on local PC only.
PS C:\> [System.Management.Automation.PSSerializer]::Deserialize('<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
>> <Obj RefId="0">
>> <TN RefId="0">
>> <T>System.Management.Automation.PSCredential</T>
>> <T>System.Object</T>
>> </TN>
>> <ToString>System.Management.Automation.PSCredential</ToString>
>> <Props>
>> <S N="UserName">Hey</S>
>> <SS N="Password">01000000d08c9ddf0115d1118c7a00c04fc297eb0100000034646a6e6b53d244b223386a302a6fe700000000020000000000106600000001000020000000db75ebc7ae7b02d84ef6cb1161559006bdd81a84ccd5d152f3a6fdfdcf102165000000000e8000000002000020000000f0c4f2676ae5a65d2823ec8d73c352c79a97d7fd3971fd64c084d90c6c94ff7c20000000476fd1bd7f1842fdfb2e2f2fc4fd17ee0d7b41fefb39cda407bd2a6176e7b40e40000000575dac900276dcc550f09fe48b341885431dd8d287a6073ccbbfbc89e2ff8ee9e3158a8d75a52332ab2a60126cbc69232c6d9109d1db17e28535726b5e1ec2b3</SS>
>> </Props>
>> </Obj>
>> </Objs>')
UserName Password
-------- --------
Hey System.Security.SecureString
[WIN-U42BH7N5O4B]: PS C:\> [System.Management.Automation.PSSerializer]::Deserialize('<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
>> <Obj RefId="0">
>> <TN RefId="0">
>> <T>System.Management.Automation.PSCredential</T>
>> <T>System.Object</T>
>> </TN>
>> <ToString>System.Management.Automation.PSCredential</ToString>
>> <Props>
>> <S N="UserName">Hey</S>
>> <SS N="Password">01000000d08c9ddf0115d1118c7a00c04fc297eb0100000034646a6e6b53d244b223386a302a6fe700000000020000000000106600000001000020000000db75ebc7ae7b02d84ef6cb1161559006bdd81a84ccd5d152f3a6fdfdcf102165000000000e8000000002000020000000f0c4f2676ae5a65d2823ec8d73c352c79a97d7fd3971fd64c084d90c6c94ff7c20000000476fd1bd7f1842fdfb2e2f2fc4fd17ee0d7b41fefb39cda407bd2a6176e7b40e40000000575dac900276dcc550f09fe48b341885431dd8d287a6073ccbbfbc89e2ff8ee9e3158a8d75a52332ab2a60126cbc69232c6d9109d1db17e28535726b5e1ec2b3</SS>
>> </Props>
>> </Obj>
>> </Objs>')
Exception calling "Deserialize" with "1" argument(s): "Key not valid for use in specified state.
"
At line:1 char:1
+ [System.Management.Automation.PSSerializer]::Deserialize('<Objs Versi ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : CryptographicException
Use Cryptographic Message Syntax encrypted strings.
Configuration WebSitePublishConfig
{
param
(
[Parameter(Mandatory=$true)]
[PSCredential] $Credential,
[Parameter(Mandatory=$true)]
[string] $CertificateFile
)
Import-DscResource -ModuleName PSDesiredStateConfiguration
$UserName = $Credential.UserName
$EncryptedPassword = $Credential.GetNetworkCredential().Password | Protect-CmsMessage -To $CertificateFile
Script MyScript
{
SetScript = `
{
# $using:UserName
$password = Unprotect-CmsMessage -Content $using:EncryptedPassword
}
}
}
If you don't care about security, pass password in plain text:
$UserName = $Credential.UserName
$PasswordPlain = $Credential.GetNetworkCredential().Password
Script MyScript
{
SetScript = `
{
# $using.UserName
# $using:PasswordPlain
}
}
Related
Getting an error on my Terraform deployment for the following. I think it's because it's using a mixture of Terraform variables and Powershell I may have confused myself on the syntax.
Here is the Code:
data "template_file" "ad-join-template" {
template = <<EOF
<powershell>
# Set-DefaultAWSRegion -Region eu-west-2
# Set-Variable -name instance_id -value (Invoke-Restmethod -uri http://169.254.169.254/latest/meta-data/instance-id)
# # New-SSMAssociation -target key=InstanceIds,Values=$instance_id -Name "${aws_ssm_document.ad-join-domain.name}"
# New-SSMAssociation `
# -Name ad-join-domain `
# -Target #{
# "Key"="InstanceIds"
# "Values"="$($instance_id)"
# }
$apiurl = "${var.API}"
$tajdns = #("${taj_dns_server[0]}","[${taj_dns_server[1]}")
$count = 0
foreach ($dns in $tajdns){
$returnedRecords = (Resolve-DnsName -Name $apiurl -Server $dns).IPAddress
New-Variable -Name "dnsRecords$count" -Value $returnedRecords -Force
$count++
}
$allDNSrecords += $dnsRecords0
$allDNSrecords += $dnsRecords1
$allDNSrecords = $allDNSrecords | Select-Object -Unique
Add-Content C:\windows\system32\drivers\etc\hosts "`n***.**.*.* ssm.eu-west-2.amazonaws.com `
`n***.**.*.* ssm.eu-west-2.amazonaws.com `
`n***.**.*.* ssm.eu-west-2.amazonaws.com `
`n***.**.*.* ssmmessages.eu-west-2.amazonaws.com `
`n***.**.*.* ssmmessages.eu-west-2.amazonaws.com `
`n***.**.*.* ssmmessages.eu-west-2.amazonaws.com `
`n$allDNSrecords[0] ${var.API}`
`n$allDNSrecords[1] ${var.API}"
$nicDetails = Get-NetAdapter
Set-DnsClientServerAddress -InterfaceIndex $nicDetails.ifIndex -ServerAddresses (${local.concat_dns_servers_join})
$domain = "${aws_directory_service_directory.ad.name}"
$password = "${aws_directory_service_directory.ad.password}" | ConvertTo-SecureString -asPlainText -Force
$username = "admin#$($domain)"
$credential = New-Object System.Management.Automation.PSCredential($username,$password)
Add-Computer -DomainName $domain -Credential $credential
Restart-Computer -Force
</powershell>
EOF
}
In the [${taj_dns_server[0]}]" this is pulling a Terraform variable out of list and populating it in to the script. Can you see if my syntax is correct?
Here is the Error:
│ Error: Invalid reference
136│
137│ on asg.tf line 19, in data "template_file" "ad-join-template":
138│ 19: $tajdns = #("[${taj_dns_server[0]}]","[${taj_dns_server[1]}]")
139│
140│ A reference to a resource type must be followed by at least one attribute
141│ access, specifying the resource name.
142╵
143╷
144│ Error: Invalid reference
145│
146│ on asg.tf line 19, in data "template_file" "ad-join-template":
147│ 19: $tajdns = #("[${taj_dns_server[0]}]","[${taj_dns_server[1]}]")
148│
149│ A reference to a resource type must be followed by at least one attribute
150│ access, specifying the resource name.
151╵
I'm trying to expose an endpoint using RestPS routes in powershell. I was able to expose it and run a custom PS script when an endpoint (http://localhost:8080/scan) is hit.
How do we pass body through routes & based on that value I need to execute the custom script.
RestPSroutes.json - Example
{
"RequestType": "GET",
"RequestURL": "/scan",
"RequestCommand": "C:/RR/api-compliance.ps1" }
snippet from the above script (api-compliance.ps1) ;
###############
# Setup Creds #
###############
$userID = "****"
$Pass = "*****"
#$Creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $userID,$Pass
$vCenter = "vCenter01"
$cluster = "Cluster01"
$vmhost = "Host01"
$bl = "Baseline01"
######################
# Connect to vCenter #
######################
Connect-VIServer $VCTRs -user $userID -password $Pass -WarningAction SilentlyContinue -Force
$baseline = Get-Baseline | ? {$_.name -eq $bl}
$baseline | Attach-Baseline -Entity $vmhost -Confirm:$false
Scan-Inventory -Entity $vmhost
Right now we are hardcoding the below values
$vCenter = "vCenter01"
$cluster = "Cluster01"
$vmhost = "Host01"
But we want these to be passed as a request body. Can someone help?
Simply declare parameters $RequestArgs and $Body in the target script/function, and RestPS will automatically pass the appropriate values along as strings:
param(
[string]$RequestArgs,
[string]$Body
)
$RequestArgs.Split('&') |ForEach-Object {
$paramName,$paramValue = $_.Split('=')
Write-Host "Received query parameter ${paramName} with value '$paramValue'"
}
The script mounts the drive correctly, but the drive is not persisted after rebooting the machine:
function RemapDrive {
param(
$DriveLetter,
$FullPath,
$Credential
)
Write-Host "Trying to remove $DriveLetter in case it already exists ..."
# $DriveLetter must be concatenated with ":" for the command to work
net use "${DriveLetter}:" /del
## $DriveLetter cannot contain ":"
$psDrive = New-PSDrive -Name "$DriveLetter" -PSProvider "FileSystem" -Root "$FullPath" -Credential $Credential -Scope "Global" -Persist
Write-Host "$DriveLetter was successfully added !"
}
function BuildCredential {
param (
$Username,
$Password
)
$pass = ConvertTo-SecureString $Password -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ($Username, $pass)
return $credential
}
$credential = (BuildCredential -Username "xxxxxx" -Password "yyyyyy")[-1]
RemapDrive -DriveLetter "X" -FullPath "\\my-server\x" -Credential $credential
What I have found:
“When you scope the command locally, that is, without dot-sourcing, the Persist parameter does not persist the creation of a PSDrive beyond the scope in which you run the command. If you run New-PSDrive inside a script, and you want the new drive to persist indefinitely, you must dot-source the script. For best results, to force a new drive to persist, specify Global as the value of the Scope parameter in addition to adding Persist to your command.”
I have tried executing the script with ". .\my-script.ps1" (to dot-source the script?), but the result is the same.
Playing around with "net use" and the registry to try to add the network drive has lead me to a cul-de-sac as well.
Specs:
Windows 10 Home
Powershell version:
Major Minor Build Revision
----- ----- ----- --------
5 1 18362 1171
Basically, New-PSDrive doesn't have the /SAVECRED parameter from net use, and will not persistently map drives as a user other than the one running the script.
There are three ways to handle this:
[Recommended] Fix the file share permissions instead of using a separate username/password, then use New-PSDrive -Name "$DriveLetter" -PSProvider "FileSystem" -Root "$FullPath" -Scope 'Global' -Persist with no credential flag. This assumes your file share allows kerberos logins, so may not work in some edge cases.
Use net use, and include the username, password, /persistent:yes and /savecred. This can be done in powershell without any issues.
Set the powershell script you already have to run at startup.
Set up your script to use the credential manager - see the answer here
Install the CredentialManager powershell module
set HKCU\Network\[drive letter]\ConnectionType = 1
set HKCU\Network\[drive letter]\DeferFlags= 4
What finally work was user19702's option #2, with a bit of extra work regarding the registration of the username and the password.
WARNING: as he mentioned, the best option (option #1) would have been "fixing the file share permissions instead of using a separate username/password". This was not possible in my case, and this is why I had to go with option #2.
This is the script:
# ---
# Helper functions:
function RemapDrive {
param(
$DriveLetter,
$Server,
$FullPath,
$Credential
)
# For net.exe to work, DriveLetter must end with with ":"
Write-Host "Trying to remove $DriveLetter in case it already exists ..."
net use "$DriveLetter" /del
# "net use" requires username and password as plain text
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($credential.Password)
$Password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
$Username=$Credential.Username
Write-Host "Registring credentials for server '$Server' ..."
cmdkey /add:$Server /user:$Username /pass:$Password
Write-Host "Mapping the drive ..."
net use $DriveLetter $FullPath /persistent:yes i
Write-Host "$DriveLetter was successfully added !"
}
function BuildCredential {
param (
$Username,
$Password
)
$pass = ConvertTo-SecureString $Password -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ($Username, $pass)
return $credential
}
# ---
# Process to execute:
$credential = (BuildCredential -Username "xxxxxx" -Password "yyyyyy")[-1]
RemapDrive -DriveLetter "X:" -Server "my-server" -FullPath "\\my-server\x" -Credential $credential
If you do not want to use a hardcoded password in BuildCredential, but you want to prompt the user instead:
function GetCredential {
param(
$Label
)
$credential = Get-Credential -Message "Write your credentials for '$Label':"
if(!$credential) {
throw "A credential was needed to continue. Process aborted."
}
return $credential
}
Also, if instead of using $Server as a param, you want to extract it from $FullPath using regex, you can do that.
It presumes the $FullPath has the following format: \\server-name\dir1\dir2\etc
# Get server name using regex:
$FullPath -match '\\\\(.*?)\\.*?'
$Server = $Matches[1]
I have a function that is used to purge a message queue on a machine but I'm looking to adapt it and make it a little more robust. I'd like to be able to fire the command to a machine even if the current machine doesn't have MSMQ installed.
The local command works without issue but when the invoke-command is called, the check to see if the queue exists returns false (even though the queue does exist). Has anyone run into anything like this before? Any suggestions?
This is my function:
Function Purge-MessageQueue
{
<#
.Synopsis
Use hostname to purge message queue
.DESCRIPTION
Checks if MSMQ is locally installed otherwise fire the purge
command to the machine you are purging
.EXAMPLE
Purge-MessageQueue -targetMachine $env:computername -queueName 'test'
#>
Param
(
[Parameter(Mandatory=$true)]
[String]$targetMachine,
[Parameter(Mandatory=$true)]
[string]$queueName
)
Write-Verbose "Purging $queueName queue on: $targetMachine"
$queueName = "$targetMachine\$queueName"
$error.clear()
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
try {[void][System.Messaging.MessageQueue]::Exists($queueName)}
catch
{
if ($_.exception.ToString() -like '*Message Queuing has not been installed on this computer.*')
{
#push command to machine
$RemoteSuccess = Invoke-Command -ComputerName $targetMachine -ScriptBlock { Param($queueName)
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
If([System.Messaging.MessageQueue]::Exists($queueName))
{
$queue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
Try{$queue.Purge()}
Catch{$error}
}
} -ArgumentList $queueName
}
}
If(!$error)
{
If([System.Messaging.MessageQueue]::Exists($queueName))
{
$queue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
$queue.Purge()
}
}
If(!$Error -and !$RemoteSuccess)
{
Write-Host "$queueName queue on $targetMachine cleared"
}
Else
{
Write-Warning "Failed locating queue $queueName on $targetMachine"
}
}
NOTES:
In order to identify what exactly is going on, I used write-host on the exists statement and it returns false. The queue is not being found when I pass the scriptblock. It is executing on the other machine (tested writing a file which succeeded). When I run:
Write-Host "$([System.Messaging.MessageQueue]::Exists($queueName))`n$queueName"
$objqueue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
I get the false, the correct queue name, and the following error:
Exception calling "Purge" with "0" argument(s): "The queue does not
exist or you do not have sufficient permissions to perform the
operation."
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : MessageQueueException
+ PSComputerName : XXXXXX
Running the same command directly on the machine works without issue.
I also found someone else trying to do something similar on serverfault:
https://serverfault.com/questions/399178/how-to-retrieve-names-of-all-private-msmq-queues-efficiently
And when I try this:
Invoke-Command -ComputerName $targetMachine -ScriptBlock { Get-MsmqQueue }
I get the following result:
Cannot find specified machine.
+ CategoryInfo : ObjectNotFound: (:) [Get-MsmqQueue], MessageQueueException
+ FullyQualifiedErrorId : MachineNotFound,Microsoft.Msmq.PowerShell.Commands.GetMSMQQueueCommand
This following command does return the data, but it doesn't allow me to send a purge command:
Invoke-Command -ComputerName $targetMachine -ScriptBlock {Get-WmiObject -class Win32_PerfRawData_MSMQ_MSMQQueue}
I also tried to write the content to a script file and then call the file, which when run on the machine, works without issue but not when called via invoke-command:
$filewriter = #"
[Reflection.Assembly]::LoadWithPartialName("System.Messaging")
If([System.Messaging.MessageQueue]::Exists('$queueName'))
{
`$objqueue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
Try{`$objqueue.Purge()}
Catch{`$error}
}
"#
$session = New-PSSession -ComputerName $targetMachine
Invoke-Command -Session $session -ScriptBlock {Param($FileWriter)
$FileWriter | Out-File "C:\Temp\PurgeQueue.ps1"
} -ArgumentList $filewriter
$test = Invoke-Command -Session $session -scriptblock {Pushd "C:\Temp\"
.\PurgeQueue.ps1}
I have not found the cause for this, but I will summarize what I have found and my workaround
Summary:
When invoking msmq commands via invoke-command, only private queues appear and can be manipulated.
Workaround:
I've build a function to deal with purging and adding message to queues by creating scheduled tasks on the remote machine to call the script created by the command.
Function Push-MSMQRemoteCommand
{
Param(
[Parameter(Mandatory=$true)]
$targetMachine,
[Parameter(Mandatory=$true)]
$password,
[Parameter(Mandatory=$true)]
$queueName,
[Switch]$purge,
$user,
$message)
Begin
{
If(!$user){$user = "$env:USERDOMAIN\$env:USERNAME"}
If($purge -and $message.Length -ne 0){Write-Error "Choose to purge or add... not both" -ErrorAction Stop}
$queuepath = "$targetMachine\$queueName"
#build commands to push
If($purge)
{
$scriptblock = #"
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
If ([System.Messaging.MessageQueue]::Exists('$queuePath')) {
`$queue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queuePath
`$queue.Purge()
}
"#
}
ElseIf($message)
{
If($message.Length -eq 0){Write-Error "No message provided to add message" -ErrorAction Stop}
$scriptblock = #"
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
`$queue = new-object System.Messaging.MessageQueue "$queuepath"
`$utf8 = new-object System.Text.UTF8Encoding
`$msgBytes = `$utf8.GetBytes('$message')
`$msgStream = new-object System.IO.MemoryStream
`$msgStream.Write(`$msgBytes, 0, `$msgBytes.Length)
`$msg = new-object System.Messaging.Message
`$msg.BodyStream = `$msgStream
`$msg.Label = "RemoteQueueManagerPowershell"
`$queue.Send(`$msg)
"#
}
#Push Commands
Invoke-Command -ComputerName $targetMachine -ScriptBlock {
Param($user,$password,$scriptblock)
$scriptblock | Out-file -FilePath "C:\temp\ManageQueue.ps1" -Force
$action = New-ScheduledTaskAction -execute 'powershell.exe' -Argument '-File "C:\temp\ManageQueue.ps1"'
#scheudling action to start 2 seconds from now
$trigger = New-ScheduledTaskTrigger -Once -At ((Get-Date)+(New-TimeSpan -Seconds 2))
Register-ScheduledTask -TaskName RemoteQueueManager `
-Action $action `
-Trigger $trigger `
-User "$user"`
-Password $password
#Start-Sleep -Seconds 10
Unregister-ScheduledTask -TaskName RemoteQueueManager -Confirm:$false
Remove-Item -Path "C:\temp\ManageQueue.ps1" -Force
} -ArgumentList $user,$password,$scriptblock
}
}
From your analysis I have feeling that it is issue of rights.
Did you check the rights for your user?
If you are a normal user you have to do the following (not an Administrator) on the
destination computer/server/VM:
1) first create a group and add there users
net localgroup "Remote PowerShell Session Users" /add
net localgroup "Remote PowerShell Session Users" the-user /add
2) Invoke GUI
Set-PSSessionConfiguration microsoft.powershell -ShowSecurityDescriptorUI
3) Add Remote PowerShell Session Users group and grant it execute (invoke) rights
4) Restart the service:
Set-PSSessionConfiguration microsoft.powershell -ShowSecurityDescriptorUI
5) the user now should be able to run remote session
The original source is here.
I am running Powershell on a remote computer that is not connected to the domain, does not have any modules and is running PS 2.0.
I want to contact the Active Directory of my domain, check if there is an entry for this computer and; if yes, delete that entry.
Checking the AD via ADSI for existance of the computer is easy. However the deleting does not work somehow.
Here is my code so far:
# Variables
$domain = "Test.com"
$Ldap = "LDAP://$domain"
$Global:AdsiSearcher = $Null
# Function to Delete PC
Function DeleteThisPc ()
{
$CurrentSearch = $Global:AdsiSearcher
$One = $CurrentSearch.FindOne()
$OPath = [adsi]$One.Path
$OPath.psbase.DeleteTree()
The Problem lies here. Even though $OPath is of type System.DirectoryServices.DirectoryEntry and the propertylist shows all properties, it does not allow me to delete the object.
Exception calling "DeleteTree" with "0" argument(s): "Logon failure:
unknown user name or bad password.
At C:\TEMP\Domjoin1.1.ps1:49 char:33 $OPath.psbase.DeleteTree <<<< ()
CategoryInfo: NotSpecified: (:) [], MethodInvocationException
FullyQualifiedErrorId : DotNetMethodException
Code:
# Function to get a ADSISearcher and set it to the global-AdsiSearcher
Function ConnectAD ()
{
$domain = new-object DirectoryServices.DirectoryEntry($Ldap,"$domain\Bob",'1234')
$filter = "(&(objectCategory=computer)(objectClass=computer)(cn=$ComputerName))"
$AdsiSearch = [adsisearcher]""
$AdsiSearch.SearchRoot = $domain
$AdsiSearch.Filter = $filter
$Global:AdsiSearcher = $AdsiSearch
}
# Main Function
Function Sub_Check-ADComputer()
{
ConnectAD
$CurSearch = $Global:AdsiSearcher.findOne()
if($CurSearch -ne $null)
{
DeleteThisPc
}
}
# Start
Sub_Check-ADComputer
Even though the issue seems to be obvious as the error states:
Logon failure: unknown user name or bad password.
The username and password is the same that I use to get the object from the AD in the first place. So it does work - do I somehow have to give the credentials again when trying to deleteTree() ? I also gave the User FullControl on the OU that the object is stored in.
Edit:
When I do it on another machine with PS 3.0 I get a different Error message:
Exception calling "DeleteTree" with "0" argument(s): "Access is
denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))"
I found the problem.
When using invoke command the variables are not transmitted unless specified by -argumentlist. Another approach I discovered was the following, which is the one I am using now and which works like a charm.
$domain = "DOMAINNAME"
$AdUser = "$domain\JoinDom"
$AdPW = "PASSWORD"
$AdPass = convertto-securestring -string $AdPW -AsPlainText -Force
$AdCred = new-object -typename System.Management.Automation.PSCredential -argumentlist $AdUser,$AdPass
$ThisComputer = $Env:COMPUTERNAME
$RetValue = $true
Function CheckExist ()
{
$ErrorActionPreference = ‘SilentlyContinue’
$Ascriptblock = $ExecutionContext.InvokeCommand.NewScriptBlock("get-adcomputer $ThisComputer")
$Ret = Invoke-Command -ComputerName SERVERNAME -ScriptBlock $Ascriptblock -Credential $AdCred
$ErrorActionPreference = ‘Continue’
return $Ret
}
$ExistBefore = CheckExist
if($ExistBefore -ne $null)
{
$scriptblock = $ExecutionContext.InvokeCommand.NewScriptBlock("Remove-ADComputer $ThisComputer")
Invoke-Command -ComputerName SERVERNAME -ScriptBlock $scriptblock -Credential $AdCred
$ExistAfter = CheckExist
if($ExistAfter -ne $null){$RetValue = $false}
}
if($RetValue -ne $false)
{
Add-computer -domainname $domain -credential $Adcred -OUPath "OU=MyOU,DC=DOMAIN,DC=DE"
Restart-Computer -Force
}
If your domain controller runs Windows Server 2008 or higher you could leverage PowerShell sessions to avoid having to work with ADSI.
Just run the following command:
Enter-PSSession -ComputerName domaincontroller.test.com -Credential (Get-Credential)
Then run Import-Module ActiveDirectory to allow you to use Get-ADComputer and Remove-ADComputer.