Handling errors when querying computer accounts - powershell

Im using this script to retrieve the OU for a list of computers. When a computer doesn't exist, I get an error:
Cannot index into a null array.
At C:\tools\scripts\get-OU5.ps1:35 char:5
+ $dn = $result.Properties["distinguishedName"]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : NullArray
When I manually searched for the computer on the domain, it didn't exist. Also, after the error occurs, it lists the previous OU value it received from the last computer, as the result for this computer that gives the error.
I know there are error handling abilities in PowerShell, but I'm just not sure on where to put the error handling and then also report it as the result in the output.

I would use the following script to retrieve the information. It uses the built-in error handling capabilities in PowerShell:
function Get-ComputerProperties{
[CmdletBinding()]
#requires -version 3
param(
[parameter(ParameterSetName = "ComputerName", Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
$ComputerName
)
begin{
Import-Module ActiveDirectory
function Get-ADParent ([string] $dn) {
$parts = $dn -split '(?<![\\]),'
$parts[1..$($parts.Count-1)] -join ','
}
}
process {
$result = $Null
try{
$result = Get-ADComputer -Filter {Name -eq $ComputerName} -ErrorAction Stop
if($result){
New-Object PSObject -Property #{"Name" = $ComputerName; "OU" = (Get-ADParent $result.DistinguishedName)}
}
} catch {
Write-Warning "Error while retrieving Computer properties for ComputerName $Computername ($($_.Exception.Message))"
}
}
end{
Remove-Module ActiveDirectory
}
}
You can use the script in the following way:
Get-ComputerProperties -ComputerName "Computer1"
or
"Computer1","Computer2" | Get-ComputerProperties
or with a text file:
Get-Content "C:\computers.txt" | %{ Get-ComputerProperties }

Related

Get-ADPrincipalGroupMembership An unspecified error has occurred

I am getting errors with Get-ADPrincipalGroupMembership command on Windows 10 (x64) machine. I have installed the required RSAT- 'Active directory Domain service and Lightweight Directory service tools' and 'Server manager' dependencies as specified int this document. I am able to execute Get-AdUser and see the results but Get-ADPrincipalGroupMembership is throwing below error.
PS C:\Users\JYOTHI> Get-ADPrincipalGroupMembership jyothi
Get-ADPrincipalGroupMembership : An unspecified error has occurred
At line:1 char:1
+ Get-ADPrincipalGroupMembership gapalani
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (jyothi:ADPrincipal) [Get-ADPrincipalGroupMembership], ADException
+ FullyQualifiedErrorId : ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADPrincipalGroupMembership
I can try the other way
(Get-Aduser jyothi -Properties MemberOf | Select MemberOf).MemberOf
but like to know what is the fix for Get-ADPrincipalGroupMembership
As you have noticed, Get-ADPrincipalGroupMembership fails with an obscure error if the reference object's name contains certain characters, or if it's a member of one or more groups that contain certain characters in their names.
I don't have definitive proof, but my testing indicates that the underlying issue is that Get-ADPrincipalGroupMembership, internally, uses ADSI and fails to correctly escape distinguished names that contain characters that need to be escaped. (If this is the case, Microsoft should be using the IADsPathname interface to escape names correctly. This would be an embarrassing oversight on their part.)
Unfortunately, this problem renders the cmdlet broken and unusable in production environments.
Here's a relatively short PowerShell script that doesn't suffer from this annoyance and also supports retrieving recursive group memberships:
# Get-ADGroupMembership.ps1
# Written by Bill Stewart
#requires -version 2
# Version history:
# 1.0 (2019-12-02)
# * Initial version. Only searches the current domain.
<#
.SYNOPSIS
Gets the distinguished names of the Active Directory groups that have a specified object as a member.
.DESCRIPTION
Gets the distinguished names of the Active Directory groups that have a specified object, represented by the -Identity parameter, as a member.
.PARAMETER Identity
Specifies an Active Directory object. You can specify either the distinguishedName or the sAMAccountName of the object.
.PARAMETER Recursive
Specifies to include the object's nested group memberships.
.NOTES
If you use the ActiveDirectory PowerShell module and want Microsoft.ActiveDirectory.Management.ADGroup objects as output, pipe this command's output to the Get-ADGroup cmdlet.
.EXAMPLE
Get the distinguished names of the groups that the kendyer account is a member of:
PS C:\> Get-ADGroupMembership kendyer
.EXAMPLE
Get the distinguished names of the groups that the kendyer account is a member of, including nested groups:
PS C:\> Get-ADGroupMembership kendyer -Recursive
.EXAMPLE
Get the ADGroup objects representing the groups that the kendyer account is a member of (requires the Active Directory module):
PS C:\> Get-ADGroupMembership kendyer | Get-ADGroup
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true,ValueFromPipeline = $true)]
[String[]] $Identity,
[Switch] $Recursive
)
begin {
$CommandName = $MyInvocation.MyCommand.Name
# Set up Pathname COM object
$ADS_ESCAPEDMODE_ON = 2
$ADS_SETTYPE_DN = 4
$ADS_FORMAT_X500_DN = 7
$Pathname = New-Object -ComObject "Pathname"
if ( -not $Pathname ) {
return
}
[Void] $Pathname.GetType().InvokeMember("EscapedMode","SetProperty",$null,$Pathname,$ADS_ESCAPEDMODE_ON)
# Outputs correctly escaped distinguished name using Pathname object
function Get-EscapedName {
param(
[String] $distinguishedName
)
[Void] $Pathname.GetType().InvokeMember("Set","InvokeMethod",$null,$Pathname,#($distinguishedName,$ADS_SETTYPE_DN))
$Pathname.GetType().InvokeMember("Retrieve","InvokeMethod",$null,$Pathname,$ADS_FORMAT_X500_DN)
}
# Outputs the memberOf attribute of an object using paged search (in case
# an object is a member of a large number of groups)
function Get-MemberOfAttribute {
param(
[String] $distinguishedName,
[Ref] $memberOf,
[Switch] $recursive
)
$searcher = [ADSISearcher] "(objectClass=*)"
$searcher.SearchRoot = [ADSI] "LDAP://$(Get-EscapedName $distinguishedName)"
$lastQuery = $false
$rangeStep = 1500
$rangeLow = 0
$rangeHigh = $rangeLow + ($rangeStep - 1)
do {
if ( -not $lastQuery ) {
$property = "memberOf;range={0}-{1}" -f $rangeLow,$rangeHigh
}
else {
$property = "memberOf;range={0}-*" -f $rangeLow
}
$searcher.PropertiesToLoad.Clear()
[Void] $searcher.PropertiesToLoad.Add($property)
$searchResults = $searcher.FindOne()
if ( $searchResults.Properties.Contains($property) ) {
foreach ( $searchResult in $searchResults.Properties[$property] ) {
if ( $memberOf.Value.Count -gt 100 ) {
Write-Progress `
-Activity $CommandName `
-Status "Getting membership of '$distinguishedName'" `
-CurrentOperation $searchResult
}
if ( $recursive ) {
if ( -not $memberOf.Value.Contains($searchResult) ) {
Get-MemberOfAttribute $searchResult $memberOf -recursive
}
}
if ( -not $memberOf.Value.Contains($searchResult) ) {
$memberOf.Value.Add($searchResult)
}
}
$done = $lastQuery
}
else {
if ( -not $lastQuery ) {
$lastQuery = $true
}
else {
$done = $true
}
}
if ( -not $lastQuery ) {
$rangeLow = $rangeHigh + 1
$rangeHigh = $rangeLow + ($rangeStep - 1)
}
}
until ( $done )
Write-Progress `
-Activity $CommandName `
-Status "Getting membership of '$distinguishedName'" `
-Completed:$true
}
function Get-ADGroupMembership {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[String] $identity,
[Switch] $recursive
)
$ldapString = $identity -replace '\\','\5c' -replace '\(','\28' -replace '\)','\29' -replace '\*','\2a' -replace '\/','\2f'
$searcher = [ADSISearcher] "(|(distinguishedName=$ldapString)(sAMAccountName=$ldapString))"
try {
$searchResults = $searcher.FindAll()
if ( $searchResults.Count -gt 0 ) {
foreach ( $searchResult in $searchResults ) {
$memberOf = New-Object Collections.Generic.List[String]
Get-MemberOfAttribute $searchResult.Properties["distinguishedname"][0] ([Ref] $memberOf) -recursive:$recursive
$memberOf
}
}
else {
Write-Error "Cannot find an object with identity '$identity'." -Category ObjectNotFound
}
}
catch {
Write-Error -ErrorRecord $_
}
finally {
$searchResults.Dispose()
}
}
}
process {
foreach ( $IdentityItem in $Identity ) {
Get-ADGroupMembership $IdentityItem -recursive:$Recursive
}
}
I've also added this script as a public gist on github in case something needs fixing or if I add new features.
Get-ADPrincipalGroupMembership -Identity "jyothi"

PowerShell Script ErrorActionPreference [duplicate]

I'm trying to export a list of all users with no photo from our Exchange Online account using powershell. I cannot get it to work and have tried various methods.
Get-UserPhoto returns this exception when there is no profile present.
Microsoft.Exchange.Data.Storage.UserPhotoNotFoundException: There is no photo stored here.
First of all I tried use Errorvariable against the command but received:
A variable that cannot be referenced in restricted language mode or a Data section is being referenced. Variables that can be referenced include the following: $PSCulture, $PSUICulture, $true, $false, and $null.
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : VariableReferenceNotSupportedInDataSection
+ PSComputerName : outlook.office365.com
Next I tried try, catch but the non-terminating error never calls the catch despite various methods followed online about setting $ErrorActionPreference first of all.
Any ideas ? Here is the script:
$UserCredential = Get-Credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
Import-PSSession $Session
$timestamp = $timestamp = get-date -Format "dd/M/yy-hh-mm"
$outfile = "c:\temp\" + $timestamp + "UserswithoutPhotos.txt"
$resultslist=#()
$userlist = get-user -ResultSize unlimited -RecipientTypeDetails usermailbox | where {$_.accountdisabled -ne $True}
Foreach($user in $userlist)
{
try
{
$user | get-userphoto -erroraction stop
}
catch
{
Write-Host "ERROR: $_"
$email= $user.userprincipalname
$name = $user.DisplayName
$office = $user.office
write-host "User photo not found...adding to list : $name , $email, $office"
$resultslist += $user
}
}
$resultslist | add-content $outfile
$resultslist
PowerShell's error handling is notoriously tricky, especially so with cmdlets that use implicit remoting via a locally generated proxy script module.
The following idiom provides a workaround based on temporarily setting $ErrorActionPreference to Stop globally (of course, you may move the code for restoring the previous value outside the foreach loop), which ensures that even functions from different modules see it:
try {
# Temporarily set $ErrorActionPreference to 'Stop' *globally*
$prevErrorActionPreference = $global:ErrorActionPreference
$global:ErrorActionPreference = 'Stop'
$user | get-userphoto
} catch {
Write-Host "ERROR: $_"
$email= $user.userprincipalname
$name = $user.DisplayName
$office = $user.office
write-host "User photo not found...adding to list : $name, $email, $office"
$resultslist += $user
} finally {
# Restore the previous global $ErrorActionPreference value
$global:ErrorActionPreference = $prevErrorActionPreference
}
As for why this is necessary:
Functions defined in modules do not see the caller's preference variables - unlike cmdlets, which do.
The only outside scope that functions in modules see is the global scope.
For more information on this fundamental problem, see this GitHub issue.
You can throw your own error like so:
try {
$error.Clear()
$user | Get-UserPhoto
if ($error[0].CategoryInfo.Reason -eq "UserPhotoNotFoundException") {throw "UserPhotoNotFoundException" }
} catch {
#code
}

Exchange Online - Get-UserPhoto - how to catch non terminating error?

I'm trying to export a list of all users with no photo from our Exchange Online account using powershell. I cannot get it to work and have tried various methods.
Get-UserPhoto returns this exception when there is no profile present.
Microsoft.Exchange.Data.Storage.UserPhotoNotFoundException: There is no photo stored here.
First of all I tried use Errorvariable against the command but received:
A variable that cannot be referenced in restricted language mode or a Data section is being referenced. Variables that can be referenced include the following: $PSCulture, $PSUICulture, $true, $false, and $null.
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : VariableReferenceNotSupportedInDataSection
+ PSComputerName : outlook.office365.com
Next I tried try, catch but the non-terminating error never calls the catch despite various methods followed online about setting $ErrorActionPreference first of all.
Any ideas ? Here is the script:
$UserCredential = Get-Credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
Import-PSSession $Session
$timestamp = $timestamp = get-date -Format "dd/M/yy-hh-mm"
$outfile = "c:\temp\" + $timestamp + "UserswithoutPhotos.txt"
$resultslist=#()
$userlist = get-user -ResultSize unlimited -RecipientTypeDetails usermailbox | where {$_.accountdisabled -ne $True}
Foreach($user in $userlist)
{
try
{
$user | get-userphoto -erroraction stop
}
catch
{
Write-Host "ERROR: $_"
$email= $user.userprincipalname
$name = $user.DisplayName
$office = $user.office
write-host "User photo not found...adding to list : $name , $email, $office"
$resultslist += $user
}
}
$resultslist | add-content $outfile
$resultslist
PowerShell's error handling is notoriously tricky, especially so with cmdlets that use implicit remoting via a locally generated proxy script module.
The following idiom provides a workaround based on temporarily setting $ErrorActionPreference to Stop globally (of course, you may move the code for restoring the previous value outside the foreach loop), which ensures that even functions from different modules see it:
try {
# Temporarily set $ErrorActionPreference to 'Stop' *globally*
$prevErrorActionPreference = $global:ErrorActionPreference
$global:ErrorActionPreference = 'Stop'
$user | get-userphoto
} catch {
Write-Host "ERROR: $_"
$email= $user.userprincipalname
$name = $user.DisplayName
$office = $user.office
write-host "User photo not found...adding to list : $name, $email, $office"
$resultslist += $user
} finally {
# Restore the previous global $ErrorActionPreference value
$global:ErrorActionPreference = $prevErrorActionPreference
}
As for why this is necessary:
Functions defined in modules do not see the caller's preference variables - unlike cmdlets, which do.
The only outside scope that functions in modules see is the global scope.
For more information on this fundamental problem, see this GitHub issue.
You can throw your own error like so:
try {
$error.Clear()
$user | Get-UserPhoto
if ($error[0].CategoryInfo.Reason -eq "UserPhotoNotFoundException") {throw "UserPhotoNotFoundException" }
} catch {
#code
}

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.

inserting productcode from msi into powershell script - running into issues with null-values

I'm writing an automated solution for a product that we package into SCCM 2012 monthly and am having some difficulty with parsing the GUID of the *.msi file into a variable to be used in my detection method script.
No matter how I come at it, I either end up with a space at the front of the variable which breaks my detection script, or a null-value error if I try and modify the variable that holds the msi guid result.
Code snippet below with the errors that I get.
--
$FILE = "$APPLOCATION\$APPNAME.MSI"
#Function to pull the GUID from the Msi
#------------
function global:Get-MsiProductCode {
param(
[parameter(Mandatory=$true)]
[IO.FileInfo]$Path,
[parameter(Mandatory=$true)]
[ValidateSet("ProductCode","ProductVersion","ProductName")]
[string]$Property
)
try {
$WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer
$MSIDatabase = $WindowsInstaller.GetType().InvokeMember("OpenDatabase","InvokeMethod",$Null,$WindowsInstaller,#($Path.FullName,0))
$Query = "SELECT Value FROM Property WHERE Property = '$Property'"
$View = $MSIDatabase.GetType().InvokeMember("OpenView","InvokeMethod",$null,$MSIDatabase,($Query))
$View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View, $null)
$Record = $View.GetType().InvokeMember("Fetch","InvokeMethod",$null,$View,$null)
$Value = $Record.GetType().InvokeMember("StringData","GetProperty",$null,$Record,1)
return $Value
}
catch {
Write-Output $_.trim().replace(' ','')
}
}
#-------------
$MSICode =(Get-MsiProductCode -Path $File -Property ProductCode)
$testoutput = "Here is a test of the msicode.$msicode"
$SCRIPTDETECT = #"
if (gwmi -Query "select * from win32_Product where IdentifyingNumber='$MSICODE'") {
if (gwmi -Query "select * from EthIL where $APPNAME`Version='$ENVIRO'") {write-host "Installed"}
else{
if (gwmi -Query "select * from EthIL where $APPNAME`64Version='$ENVIRO'") {write-host "Installed"}
else {}
}
}
else{}
"#
write-output $msicode
write-output $SCRIPTDETECT
write-host $testoutput
--
the output to the above code looks as follows
{713335BD-14AB-460D-92C1-196DBE000D73}
if (gwmi -Query "select * from win32_Product where IdentifyingNumber=' {713335BD-14AB-460D-92C1-196DBE000D73}'") {
if (gwmi -Query "select * from EthIL where EthILVersion='SDPRODCOPY'") {write-host "Installed"}
else{
if (gwmi -Query "select * from EthIL where EthIL64Version='SDPRODCOPY'") {write-host "Installed"}
else {}
}
}
else{}
here is a test of the msicode. {713335BD-14AB-460D-92C1-196DBE000D73}
--
so as you can see the first echo of msicode brings back the GUID without a space. the use of $MSICODE as part of a string adds a space to the string before $MSICODE is called.
If I try and modify $MSICODE to perhaps trim the spaces or replace the spaces with the below code
$msicode = $msicode.replace(' ','')
I get the following error
You cannot call a method on a null-valued expression.
At line:45 char:1
+ $msicode = $msicode.replace(' ','')
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
--
Please help!! I'm so lost with the null-value, but my brain just can't comprehend why a space is being place before the value when there isn't one in the output!!!
UPDATE
I have found a way to get the result I am after using psmsi using the below code.
$msicode = get-msitable $File -table Property | where-object { $_.Property -eq "ProductCode" } | select-object -expand Value
This has now resolved my issue, however I am still curious as to why my function performs as it does.