Moving computer to another OU in OSD with PowerShell - powershell

I am having a lot of trouble getting this code to work in a TS environment.
From a Windows environment, it works great. Just about any variation you will find with a google search of this code works fine in Windows. However in a Task Sequence these variations just produce different error messages for why the computer can't be moved.
Unspecified Error
The specified domain either doesn't exist or couldn't be contacted
Instance of an object not set to an object
I feel that something is happening when we get to the psbase.MoveTo() method call. Up to that point, it can print something out that looks like a reasonable object. In other words, they aren't null or something.
Then psbase.MoveTo() says no.
Example code.
$logFile = "MoveComputerLog.txt"
# Domain Credentials
$account = "domain\osdaccount"
$password = "thepassword"
function logMessage {
param ([string]$logstring)
Write-Host $logstring
Add-content $logFile -value $logstring
}
$computerName = "COMPUTERNAME"
logMessage "computerName: $computerName"
$root = "LDAP://sweet.domain.com"
$domain = New-Object System.DirectoryServices.DirectoryEntry($root, $account, $password)
$search = New-Object System.DirectoryServices.DirectorySearcher($domain)
$search.filter = "(&(objectClass=computer)(name=$computerName))"
$result = $search.findall()
$computerDN = $result.Properties.Item("DistinguishedName")
logMessage "DN: $computerDn"
$computer = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$computerDN", $account, $password)
logMessage "Computer: $computer"
$destination = "LDAP://ou=here,ou=goes,ou=it,dc=sweet,dc=domain,dc=com"
$ou = New-Object System.DirectoryServices.DirectoryEntry($destination, $account, $password)
try {
# "The specified domain couldn't be connected or doesn't exist."
$computer.psbase.MoveTo($ou.Path)
} catch {
Write-Host "Encountered error while moving $computerName"
logMessage $error[0]
}

Here is the definition, ADSI is important:
PS C:\> $computer.psbase.MoveTo
OverloadDefinitions
-------------------
void MoveTo(adsi newParent)
void MoveTo(adsi newParent, string newName)
You can try this, rigth after $result = $search.findall() :
$computer = [ADSI]$result.path
$computer.psbase.Moveto( [ADSI]LDAP://ou=here,ou=goes,ou=it,dc=sweet,dc=domain,dc=com )

Related

Add a computer to an AD group on a specific domain controller via ADSI adapter

I run this script in a user context that has privileges to add members to my AD group. I verified permissions already and I can add members manually via ADUC.
I'd like to add my machine to a specific group on a specific domain controller. I'm very unfamiliar with ADSI usage and I pieced together the below script based on other examples. I'm unable to use PS AD module at the time this script will be ran.
Param(
[Parameter(Mandatory)]
[string]$GroupName
)
#Find domain controllers
$searcher = New-Object System.DirectoryServices.DirectorySearcher([adsi] "LDAP://OU=Domain Controllers,DC=corp,DC=thing,DC=com")
$searcher.Filter = "(objectclass=computer)"
$DomainControllers = $searcher.FindAll()
Write-Verbose "Found DCs:"
foreach ($dc in $DomainControllers.Properties.cn)
{
Write-Verbose "$dc"
}
$TargetController = $null
$ComputerDn = $null
foreach ($dc in $DomainControllers.Properties.cn)
{
$searcher = New-Object System.DirectoryServices.DirectorySearcher([adsi] "LDAP://$dc/DC=corp,DC=thing,DC=com")
$searcher.Filter = "(&(objectclass=computer)(cn=$env:COMPUTERNAME))"
$result = $searcher.FindOne()
try {
if ($result)
{
$TargetController = $dc
Write-Verbose "Target controller set: $TargetController"
$ComputerDn = $result.Properties.distinguishedname
Write-Verbose "Computer DN: $ComputerDn"
break
}
else
{
Write-Verbose "Did not find $env:COMPUTERNAME on $dc"
}
}
catch
{
Write-Verbose "$dc ERROR"
}
}
if ($TargetController)
{
$GroupSearcher = New-Object System.DirectoryServices.DirectorySearcher([adsi] "LDAP://$TargetController/DC=corp,DC=thing,DC=com")
$GroupSearcher.Filter = "(&(objectclass=group)(cn=$GroupName))"
$GroupDn = $GroupSearcher.FindOne().Properties.distinguishedname
$Group = [ADSI] "LDAP://$TargetController/$GroupDn"
$ComputerSearcher = New-Object System.DirectoryServices.DirectorySearcher([adsi] "LDAP://$TargetController/DC=corp,DC=thing,DC=com")
$ComputerSearcher.Filter = "(&(objectclass=computer)(cn=$env:COMPUTERNAME))"
$result = $ComputerSearcher.FindOne().Properties.memberof -match "cn=$GroupName,"
if (!$result)
{
try
{
$Computer = [adsi] "LDAP://$TargetController/$ComputerDn"
$Group.Add("$Computer")
}
catch
{
$_.Exception.Message ; Exit 1
}
}
else
{
Write-Verbose "$env:COMPUTERNAME already a member of $GroupName"
}
}
Running this I get the error "Exception calling "Add" with "1" argument(s): "Exception from HRESULT: 0x80005000". I'm open to any alternatives!
As Bill Stewart commented you should either remove the [adsi] accelerator
$Computer = "LDAP://$TargetController/$ComputerDn"
$Group.Add($Computer)
or specify the path
$Computer = [adsi] "LDAP://$TargetController/$ComputerDn"
$Group.Add($Computer.path)
The method expects the path which you've already constructed with "LDAP://$TargetController/$ComputerDn" which makes the cast to an adsi object unnecessary.

Problem with function calling function in PowerShell

I have two functions:
CreateComputer-Group
CreateCoomputer-Role
Once first function executes then GroupCreated in first function is used in second function to create computer role. How can I make sure first function execution is completed and then only second function to execute.
Here is the code:
$global:usergroup = "TACACS Admins"
$global:computerrole = "123413-NPARC"
$global:zone = "AWS"
$username = "<>"
$password = "<>"
[String[]] $global:HostServers = 'smp001-01','sl1ps01-13-8'
#[String[]] $global:HostServers = $hostServer.Replace("'","").Split(",")
Import-Module ActiveDirectory
Import-Module Centrify.DirectControl.PowerShell
$Password = ConvertTo-SecureString $password -AsPlainText -Force
$global:Cred = New-Object System.Management.Automation.PSCredential($username, $Password)
Set-CdmCredential -Domain test.com -Credential $Cred
function CreateComputer-Group {
Param($Cred,$zone,$computerrole)
try {
New-ADGroup -Path "ou=Role Groups-Computer,ou=Centrify,ou=Operations,dc=qateradatacloud,dc=com" -Name $computerrole -GroupScope Global -GroupCategory Security -Credential $Cred -ErrorAction Stop
} catch {
$ErrorMessage = $_.Exception
return $ErrorMessage
break
}
}
function create-computerRole {
try {
$ADGroupName = Get-ADGroup -Identity $computerrole
Write-Host "********** Get Command Outout *********"
Write-Host $ADGroupName
Write-Host $CustomerZone
Write-Host $computerrole
$global:Hellow = New-CdmComputerRole -Zone $CustomerZone -Name $computerrole -Group $ADGroupName
Write-Host $Hellow
} catch {
$ErrorMessage = $_.Exception
return $ErrorMessage
}
}
Not sure why New-CdmComputerRole command showing no such object on server.
Here is the output:
********** Get Command Outout *********
CN=123413-NPARC,OU=Role Groups-Computer,OU=Centrify,OU=Operations,DC=qateradatacloud,DC=com
CN=AWS,CN=qateradatacloud,CN=Zones,OU=Centrify,OU=Operations,DC=qateradatacloud,DC=com
123413-NPARC
System.DirectoryServices.DirectoryServicesCOMException (0x80072030): There is no such object on the server.
at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectoryEntry.Bind()
at System.DirectoryServices.DirectoryEntry.RefreshCache(String[] propertyNames)
at Centrify.DirectControl.Util.AD.DirectoryEntryEx.RefreshCache(DirectoryEntry de, String[] propertyNames)
at Centrify.DirectControl.Util.ActiveDirectory.Session.GetDirectoryEntryCheckOffline(String domainDcIpNetbios, String dn, String[] propertiesToLoad)
at Centrify.DirectControl.Util.ActiveDirectory.Session.GetDirectoryEntry(String domainDcIpNetbios, String dn, String[] propertiesToLoad)
at Centrify.DirectControl.Util.ActiveDirectory.Session.GetDirectoryEntry(String dn, String[] propertiesToLoad)
at Centrify.DirectControl.PowerShell.Types.CdmAdPrincipal.BindDirectoryEntry(Session session)
at Centrify.DirectControl.PowerShell.Types.CdmAdObject.Bind(Session session)
at Centrify.DirectControl.PowerShell.Commands.NewCdmComputerRole.InnerBeginProcessing()
at Centrify.DirectControl.PowerShell.CmdletBase.BeginProcessing()
at System.Management.Automation.Cmdlet.DoBeginProcessing()
at System.Management.Automation.CommandProcessorBase.DoBegin()
Finally I had split entire script into two scripts. One for Create AG-Group and another for all Centrify Commands

Supress error in powershell and display custom error message

I have created a PowerShell script that will add a VPN connection for Cisco Meraki.
The script itself functions as intended, but if a error occures, the "Completed" popup appears, with the error message shown in the PS windows.
Is it possible to supress the error and show a custom error popup based on the error that appears, while stopping the "Completed" popup from appearing?
I am aware of the $ErrorActionPreference= 'silentlycontinue', but unsure of how to implement this with a custom error.
Script to add VPN connections for Cisco Meraki.
$Name = Read-Host -Prompt 'Enter the profile name for this VPN connection'
$password = Read-Host -assecurestring "Please enter your Pre-shared Key"
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
Add-VpnConnection -Name "$Name" -ServerAddress 193.214.153.2 -AuthenticationMethod MSChapv2 -L2tpPsk "$password" -TunnelType L2tp -RememberCredential -Force
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("VPN-profile for $Name has been created.
You may now use this connection.
Username and password is required on first time sign on.
Support: _witheld_ | _witheld_",0,"Completed")
Since your script continues to run after the error occurs, you are dealing with a non-terminating error, so you can use the -ErrorVariable common parameter to capture a given cmdlet invocation's error(s).
Using a simplified example, which you can apply analogously to your Add-VpnConnection call:
# Call Get-Item with a nonexistent path, which causes a *non-terminating* error.
# * Capture the error with -ErrorVariable in variable $err.
# * Suppress the error console output with -ErrorAction SilentlyContinue
Get-Item /NoSuch/Path -ErrorVariable err -ErrorAction SilentlyContinue
$null = (New-Object -ComObject Wscript.Shell).Popup(
$(if ($err) { "Error: $err" } else { 'Success.' })
)
If you were dealing with a terminating error, you'd have to use try / catch:
# Call Get-Item with an unsupported parameter, which causes a
# *(statement-)terminating* error.
try {
Get-Item -NoSuchParam
} catch {
# Save the error, which is a [System.Management.Automation.ErrorRecord]
# instance. To save just a the *message* (a string), use
# err = "$_"
$err = $_
}
$null = (New-Object -ComObject Wscript.Shell).Popup(
$(if ($err) { "Error: $err" } else { 'Success.' })
)
Note:
Neither -ErrorAction nor -ErrorVariable work with terminating errors.
Conversely, try / catch cannot be used to handle non-terminating errors, which is presumably why Ranadip Dutta's answer didn't work for you.
For a comprehensive discussion of PowerShell error handling, see this GitHub issue.
You have to have the error handling for the script. I have given it as a whole in the below script but you can configure it based on your need:
try
{
$Name = Read-Host -Prompt 'Enter the profile name for this VPN connection'
$password = Read-Host -assecurestring "Please enter your Pre-shared Key"
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
Add-VpnConnection -Name "$Name" -ServerAddress 193.214.153.2 -AuthenticationMethod MSChapv2 -L2tpPsk "$password" -TunnelType L2tp -RememberCredential -Force
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("VPN-profile for $Name has been created.You may now use this connection.Username and password is required on first time sign on.Support: _witheld_ | _witheld_",0,"Completed")
}
catch
{
"Your custom message"
$_.Exception.Message
}
For further refence, read TRY/CATCH/FINALLY in Powershell
Hope it helps.

Unable to create printers on Windows 2008 using WMI Class

When using the below code to create printer on Windows 2008 servers to create the printers
`function CreatePrinterPort {
$server = $args[0]
$port = ([WMICLASS]“\\$server\ROOT\cimv2:Win32_TCPIPPrinterPort").createInstance()
$port.Name= $args[1]
$port.SNMPEnabled=$false
$port.Protocol=2
$port.HostAddress= $args[2]
$port.Put()
}
function CreatePrinter {
$server = $args[0]
$print = ([WMICLASS]“\\$server\ROOT\cimv2:Win32_Printer”).createInstance()
$print.Drivername = $args[1]
$print.PortName = $args[2]
$print.Shared = $true
$print.Published = $false
$print.Sharename = $args[3]
$print.Location = $args[4]
$print.Comment = $args[5]
$print.DeviceID=$args[2]
$print.Put()
}
$printers = Import-Csv “C:\printers.csv”
foreach ($printer in $printers) {
CreatePrinterPort $printer.Printserver $printer.Portname $printer.IPAddress
CreatePrinter $printer.Printserver $printer.Driver $printer.Portname $printer.Sharename $printer.Location $printer.Comment $printer.Printername
}'
I am getting the following error. The port creation function is working.
"IsSingleton : False
Exception calling "Put" with "0" argument(s): "Generic failure "
At line:23 char:1
+ $print.Put()
+ ~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException"
I have importing all the details from a CSV file and it contains all the information.
Any suggestions?
I'm pretty sure you're both doing this the hard way, and didn't read the MSDN page for the Win32_Printer class. It says in the remarks that you have to enable the SeDriverUpdate privilege before you can issue the Put method, so I have a feeling that's where you're getting your error from.
Next, use the Set-WMIInstance cmdlet, or the newer 'New-CIMInstance` if you can. Calling the classes directly is possible I'm sure, but if the server is local it won't enable the privileges that you need to create a printer.
Lastly, you could make your function better if you made it an advanced function, and allowed piped data. Check this out:
function CreatePrinter {
[cmdletbinding()]
Param(
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Alias('Printserver')]
$Server,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Alias('Driver')]
$Drivername,
[Parameter(ValueFromPipelineByPropertyName=$true)]
$PortName,
[Parameter(ValueFromPipelineByPropertyName=$true)]
$Sharename,
[Parameter(ValueFromPipelineByPropertyName=$true)]
$Location,
[Parameter(ValueFromPipelineByPropertyName=$true)]
$Comment,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Alias('IPAddress')]
$HostAddress,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Alias('PrinterName')]
$Name
)
PROCESS{
$PortArgs = #{
Name = $PortName
SNMPEnabled = $false
Protocol = 2
HostAddress = $HostAddress
}
Set-WmiInstance -Class Win32_TCPIPPrinterPort -Arguments $PortArgs -ComputerName $Server -PutType UpdateOrCreate -EnableAllPrivileges
$PrinterArgs = #{
Drivername = $Drivername
PortName = $PortName
Shared = $true
Published = $false
Sharename = $Sharename
Location = $Location
Comment = $Comment
DeviceID = $PortName
Name = $Name
}
Set-WmiInstance -Class Win32_Printer -Arguments $CmdArgs -ComputerName $Server -PutType UpdateOrCreate -EnableAllPrivileges
}
}
That creates the port, and then the printer. I suppose you could split it into two functions, but do you really see needing one without the other? Plus you can pipe your CSV data directly into it like this:
Import-CSV "C:\printers.csv" | CreatePrinter
That's it, that will create ports and printers for all records in the CSV. Plus I used the UpdateOrCreate enum, so if something isn't right you could correct the CSV and just run it again to refresh the settings to the correct settings without having to worry about deleting old copies and making new copies of things.

Capturing errors in this Powershell script

I have this test script to change the Administrator password on a list of servers.
I have set the script to log errors if the server can't be ping'd or account can't be found etc. However in addtion to this i'd like to capture any other errors that take place and also add those to the log file. I know you can use the "Try and Catch" for error handling but havn't had any luck so far.
Would someone be kind enough to show how to do it?
Here is the script
$date = Get-Date
$user = "Administrator"
$newpwd = "MyPassword"
$servers = gc C:\servers.txt
foreach ($server in $servers)
{
$ping = new-object System.Net.NetworkInformation.Ping
$Reply = $null
$Reply = $ping.send($server)
if($Reply.status -like 'Success')
{
$Admin=[adsi]("WinNT://" + $server + "/$user, user")
if($?)
{
$Admin.SetPassword($newpwd)
if($?)
{Add-Content -path C:\Audit\logs\servers-reset.csv -Value "$server, Succsess the $user password was changed. , $date"}
else
{Add-Content -path C:\Audit\logs\servers-reset.csv -Value "$server, Error: FAILED to change the password. , $date"}
}
else
{
Add-Content -path C:\Audit\logs\servers-reset.csv -Value "$server, Error: The $user user account was not found on the server. , $date"}
}
else
{
Add-Content -path C:\Audit\logs\servers-reset.csv -Value "$server, Error: Ping FAILED could not connect. , $date"
}
If you want to write exceptions to the log right after they were thrown, you could use a trap. Add something like this to you script:
trap [Exception] {
#Add message to log
Add-Content -Path test.csv -Value "$server, $($_.Exception.Message), $(Get-Date)"
#Continue script
continue;
}
That will log all exceptions (not all errors).
If you want all errors, you can access them using $Error. It's an arraylist containing every error during your sessions(script). The first item $Error[0] is the latest error. This however, is not something that fits directly into an csv file without formatting it.