Powershell checking if OU exist - powershell

I'm trying to check if an OU exist before creating it. My problem is that I have 2 mother OU "USER BY SITE" and "GROUP BY SITE", and I need to have the exact same OU in those 2, 1 for storing users, the other for storing groups.
So far I used this function :
function CheckOUExist
{
param($OUToSeek)
$LDAPPath = "LDAP://dc=Domain,dc=local"
$seek = [System.DirectoryServices.DirectorySearcher]$LDAPPath
$seek.Filter = “(&(name=$OUToSeek)(objectCategory=organizationalunit))”
$Result = $seek.FindOne()
return $Result
}
There is my problem, I always get the OU existing in "GROUP BY SITE" even if $LDAPPath = "OU=USERS BY SITE,DC=Domain,DC=local". Am I missing something there? Is there a way to for the [System.DirectoryServices.DirectorySearcher] to work only in the OU I gived in the $LDAPPath?
If you need more accurate detail, I'll gladly provide them.
Thank you in advance.

Try the Exists method, you get back true/false respectively:
[adsi]::Exists("LDAP://OU=test,DC=domain,DC=com")

The following, as suggested by Shay, works great if you're working with clean data.
[string] $Path = 'OU=test,DC=domain,DC=com'
[adsi]::Exists("LDAP://$Path")
Thanks for this great starting point! However, if you're verifying potentially unclean data, you'll get thrown an error. Some examples of possible errors are:
If the something isn't formatted properly
(ERR: An invalid dn syntax has been specified)
If the domain doesn't exist
(ERR: The server is not operational)
If the domain won't communicate with you
(ERR: A referral was returned from the server)
All of these errors should be caught with [System.Management.Automation.RuntimeException] or you can just leave the catch statement blank to catch all.
Quick Example:
[string] $Path = 'OU=test,DC=domain,DC=com'
try {
$ou_exists = [adsi]::Exists("LDAP://$Path")
} catch {
# If invalid format, error is thrown.
Throw("Supplied Path is invalid.`n$_")
}
if (-not $ou_exists) {
Throw('Supplied Path does not exist.')
} else {
Write-Debug "Path Exists: $Path"
}
More details:
http://go.vertigion.com/PowerShell-CheckingOUExists

The problem is the construction of the DirectorySearcher object. To properly set the search root, the DirectorySearcher needs to be constructed using a DirectoryEntry object ([ADSI] type accelerator), whereas you are using a string. When a string is used, the string is used as the LDAP filter and the search root is null, causing the searcher to use the root of the current domain. That is why it looks like it isn't searching the OU you want.
I think you will get the results you are looking for if you do something like the following:
$searchroot = [adsi]"LDAP://OU=USERS BY SITE,DC=Domain,DC=local"
$seek = New-Object System.DirectoryServices.DirectorySearcher($searchroot)
$seek.Filter = "(&(name=$OUToSeek)(objectCategory=organizationalunit))"
... etc ...
Notice that a DirectoryEntry is first constructed, which is then used to construct the DirectorySearcher.

How about:
#Requires -Version 3.0
# Ensure the 'AD:' PSDrive is loaded.
if (-not (Get-PSDrive -Name 'AD' -ErrorAction Ignore)) {
Import-Module ActiveDirectory -ErrorAction Stop
if (-not (Get-PSDrive -Name 'AD' -ErrorAction Silent)) {
Throw [System.Management.Automation.DriveNotFoundException] "$($Error[0]) You're likely using an older version of Windows ($([System.Environment]::OSVersion.Version)) where the 'AD:' PSDrive isn't supported."
}
}
Now that the AD: PSDrive is loaded, we have a couple of options:
$ou = "OU=Test,DC=Contoso,DC=com"
$adpath = "AD:\$ou"
# Check if this OU Exist
Test-Path $adpath
# Throw Error if OU doesn't exist
Join-Path 'AD:' $ou -Resolve
More info on this topic: Playing with the AD: Drive for Fun and Profit

Import-Module ActiveDirectory
Function CheckIfGroupExists{
Param($Group)
try{
Get-ADGroup $Group
}
catch{
New-ADGroup $Group -GroupScope Universal
}
}
Will also work

Related

Foreach write output when one or more fail

Currently I have this script:
$AdminSiteURL="https://contoso-admin.sharepoint.com"
$SiteURL=""
$UserID="klaas.hansen#contoso.nl"
$sitecollectios = #("https://contoso.sharepoint.com/sites/Extranet","https://contoso.sharepoint.com/sites/contoso","https://contoso.sharepoint.com/sites/Projecten","https://contoso.sharepoint.com/sites/PFO","https://contoso.sharepoint.com/sites/beheer","https://contoso.sharepoint.com/sites/Intranet")
#Get Credentials to connect
$Cred = Get-Credential
#Connect to SharePoint Online Admin Site
Connect-SPOService -Url $AdminSiteURL -Credential $cred
foreach ($collectie in $sitecollectios)
{
Get-SPOUser -Site $collectie -LoginName $UserID
}
When it can't find the user however the foreach shows an error. which is obvious. Is it possible to when it can't find the user in one or more of the site collections it shows me an error in write output, but not every time it can't find it. so for example it can't find the user in 3 site collections it only has to show me once that it can't find it.
Mathias R. Jessen's solution is effective, but there's a simpler and faster alternative:
The -ErrorVariable common parameter has a rarely seen feature that allows you to append the errors collected during command execution to an existing variable, simply by prepending + to the target variable name, which enables the following solution:
foreach ($collectie in $sitecollectios)
{
# Using built-in alias parameter names, you could shorten to:
# Get-SPOUser -ea SilentlyContinue -ev +errs ...
Get-SPOUser -ErrorAction SilentlyContinue -ErrorVariable +errs -Site $collectie -LoginName $UserID
}
# Print the errors that occurred.
$errs
-ErrorAction SilentlyContinue silences the errors (do not use Ignore, as that would suppress the errors altogether).
-ErrorAction +errs collects any error(s) in variable $errs, by either appending to the existing collection in $errs or by creating one on demand.
Note how the variable name, errs must not be prefixed with $ when passed to -ErrorAction.
Afterwards, you can examine the $errs collection to see for which users the call failed.
$errs (like the automatic $Error variable that collects errors session-wide) will be an array-like object (of type System.Collections.ArrayList) containing System.Management.Automation.ErrorRecord objects.
The simplest way to get the error message (short of simply printing $errs as a whole to the screen) is to call .ToString() on an error record; e.g., $errs[0].ToString(); to get all error messages in the collection, use $errs.ForEach('ToString'). There is the .Exception.Message property, but that can situationally be overruled by .ErrorDetails.Message when the error prints to the display; .ToString() applies this logic automatically.
The .TargetObject property tells you the target object or input that triggered the error; I can't personally verify what Get-SPOUser does, but it would make sense for the -LoginName argument of a non-existing users to be reflected there; this is how it works analogously with Get-Item -Path NoSuch, for instance: in the resulting error record, .TargetObject contains 'NoSuch' resolved to a full path.
Catch the errors inline and then report on number of errors caught at the end:
$FailedCollections = #()
Connect-SPOService -Url $AdminSiteURL -Credential $cred
foreach ($collectie in $sitecollectios)
{
try{
Get-SPOUser -Site $collectie -LoginName $UserID -ErrorAction Stop
}
catch{
$FailedCollections += $collectie
}
}
if($FailedCollections.Count -ge 1){
Write-Error "Errors encounted in $($FailedCollections.Count) collections: [$($FailedCollections -join ', ')]"
}

ADSI/System.DirectoryServices.DirectorySearcher result parsing

A trivial question, but hopefully really obvious for those who know.
Search constructor:
$Search = New-Object System.DirectoryServices.DirectorySearcher
(([adsi]"LDAP://ou=Domain Users,dc=example,dc=pri"),'(objectCategory=person)',
('name','employeeID'))
I want to exclude results where the employeeID attribute does not exist.
This works:
$users = $Search.FindAll()
ForEach ($u in $users) {
If ($u.properties.employeeid) {
Write-Host $($u.properties.name)
}
}
The following does not work - no output. However, when the IF statement is omitted, results are output.
ForEach ($user in $($Search.FindAll())) {
If ($user.properties.employeeID) {
Write-Host $($user.properties.name)
}
}
Is it a syntax issue in the second example, or do I just need to temporarily store results in an object before running conditional statements on them?
(To circumvent any discussion on why not use the ActiveDirectory module and Get-ADUser, it's for a user that cannot have the module installed on their workstation, nor be granted perms to invoke it via a PSSession on a host where it is installed.)
Update: found a slightly nicer way of doing the where clause:
$searcher.FindAll() | where { ($_.properties['employeeid'][0]) }
Just remove if statement and filter search results:
$users = $Search.FindAll() | Where-Object {-not [string]::IsNullOrEmpty($_.properties.employeeID)}

Need to check for the existence of an account if true skip if false create account

I am trying to create local user on all servers and I want to schedule this as a scheduled task so that it can run continually capturing all new servers that are created.
I want to be able to check for the existence of an account and if true, skip; if false, create account.
I have imported a module called getlocalAccount.psm1 which allows me to return all local accounts on the server and another function called Add-LocaluserAccount
which allows me to add local accounts these work with no problems
when I try and run the script I have created the script runs but does not add accounts
Import-Module "H:\powershell scripts\GetLocalAccount.psm1"
Function Add-LocalUserAccount{
[CmdletBinding()]
param (
[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]$ComputerName=$env:computername,
[parameter(Mandatory=$true)]
[string]$UserName,
[parameter(Mandatory=$true)]
[string]$Password,
[switch]$PasswordNeverExpires,
[string]$Description
)
foreach ($comp in $ComputerName){
[ADSI]$server="WinNT://$comp"
$user=$server.Create("User",$UserName)
$user.SetPassword($Password)
if ($Description){
$user.Put("Description",$Description)
}
if ($PasswordNeverExpires){
$flag=$User.UserFlags.value -bor 0x10000
$user.put("userflags",$flag)
}
$user.SetInfo()
}
}
$usr = "icec"
$rand = New-Object System.Random
$computers = "ServerA.","ServerB","Serverc","ServerD","ServerE"
Foreach ($Comp in $Computers){
if (Test-Connection -CN $comp -Count 1 -BufferSize 16 -Quiet){
$admin = $usr + [char]$rand.next(97,122) + [char]$rand.next(97,122) + [char]$rand.next(97,122) + [char]$rand.next(97,122)
Get-OSCLocalAccount -ComputerName $comp | select-Object {$_.name -like "icec*"}
if ($_.name -eq $false) {
Add-LocalUserAccount -ComputerName $comp -username $admin -Password "password" -PasswordNeverExpires
}
Write-Output "$comp online $admin"
} Else {
Write-Output "$comp Offline"
}
}
Why bother checking? You can't create an account that already exists; you will receive an error. And with the ubiquitous -ErrorAction parameter, you can determine how that ought to be dealt with, such as having the script Continue. Going beyond that, you can use a try-catch block to gracefully handle those exceptions and provide better output/logging options.
Regarding your specific script, please provide the actual error you receive. If it returns no error but performs no action check the following:
Event Logs on the target computer
Results of -Verbose or -Debug output from the cmdlets you employ in your script
ProcMon or so to see what system calls, if any, happen.
On a sidenote, please do not tag your post with v2 and v3. If you need a v2 compatible answer, then tag it with v2. Piling on all the tags with the word "powershell" in them will not get the question answered faster or more effectively.
You can do a quick check for a local account like so:
Get-WmiObject Win32_UserAccount -Filter "LocalAccount='true' and Name='Administrator'"
If they already exist, you can either output an error (Write-Error "User $UserName Already exists"), write a warning (Write-Warning "User $UserName Already exists"), or simply silently skip the option.
Please don't use -ErrorAction SilentlyContinue. Ever. It will hide future bugs and frustrate you when you go looking for them.
This can very easily be done in one line:
Get-LocalUser 'username'
Therefore, to do it as an if statement:
if((Get-LocalUser 'username').Enabled) { # do something }
If you're not sure what the local users are, you can list all of them:
Get-LocalUser *
If the user is not in that list, then the user is not a local user and you probably need to look somewhere else (e.g. Local Groups / AD Users / AD Groups
There are similar commands for looking those up, but I will not outline them here

Checking if Distribution Group Exists in Powershell

I am writing a script to quickly create a new distribution group and populate it with a CSV. I am having trouble testing to see if the group name already exists.
If I do a get-distributiongroup -id $NewGroupName and it does not exist I get an exception, which is what I expect to happen. If the group does exist then it lists the group, which is also what I expect. However, I can not find a good way to test if the group exists before I try to create it. I have tried using a try/catch, and also doing this:
Get-DistributionGroup -id $NewGroupName -ErrorAction "Stop"
which makes the try/catch work better (as I understand non-terminating errors).
Basically, I need to have the user enter a new group name to check if it is viable. If so, then the group gets created, if not it should prompt the user to enter another name.
You can use SilentlyContinue erroraction so that no exception/error shows:
$done = $false
while(-not $done)
{
$newGroupName = Read-Host "Enter group name"
$existingGroup = Get-DistributionGroup -Id $newGroupName -ErrorAction 'SilentlyContinue'
if(-not $existingGroup)
{
# create distribution group here
$done = $true
}
else
{
Write-Host "Group already exists"
}
}
This should do the trick:
((Get-DistributionGroup $NewGroupName -ErrorAction 'SilentlyContinue').IsValid) -eq $true

Checking several Hyper-V Hosts for a specific VM in Powershell

I am writing a script to administer Hyper-VMs using the PowerShell Management Library for Hyper-V.
Since we are using several Hyper-V Hosts and our VMs can change their host for performance reasons or other reasons I need a script that finds out which Host a VM runs on for the following functions.
This was my try at accomplishing this:
function IdentifyHost
{
param
(
[parameter(Position=0, Mandatory = $true)]
[ValidateNotNullOrEmpty()]
$VM
)
[Array]$hosts=Get-VMHost
if ($hosts.count -eq 0)
{
Write-Warning "No valid hosts found."
}
for ([int]$i=0; $i -lt $hosts.count; $i++ )
{
try
{
$out = Get-VM -Name $VM -Server $hosts[$i] -ErrorAction Stop
}
catch [UnauthorizedAccessException]
{
Write-Warning "Access to $hosts[$i] denied."
}
if ($VM -is [String])
{
if ($out.VMElementName -eq $VM )
{
return $out.__SERVER
}
}
elseif ($VM.ElementName -ne $null)
{
if ($out.VMElementName -eq $VM.VMElementName)
{
return $out.__SERVER
}
}
}
Write-Warning "No Host found for $VM"
}
Get-VMHost returns an array of all available Hyper-V hosts in the local area network.
My problem is that my function always returns the first element of the $hosts array whenever there is an UnauthorizedAccessException for the first element.
The plan is as following:
If the VM exists on the Host he will return a WMI Object representing that VM whose VMElementName property is equal to the VMs name given as parameter.
If the VM is given a WMI Object representing a VM the VMElementName properties of the two objects are equal.
If the VM does not exist on the Host he returns nothing.
If there's an access issue it should be catched.
But somehow it doesn't work out.
My question is this: What am I doing wrong in the code? And how can I fix it?
EDIT: The output of the function is the access problem warning for the first element of the $hosts array and then the first element of $hosts itself.
EDIT2: I fixed this myself by changing the return from the fragile $hosts[$i] to $out.__Server
Okay so I found a possible way of solving this issue:
Instead of returning the $hosts[$i] which yields unfavorable results I return the __Server property of $out, assuming there is a valid $out that matches the conditions.
If any of you guys knows a better or cleaner way of doing this, please by my guest.