I am getting my feet wet with PS classes, by trying to implement a path type identifier class. Basically I want to pass a string in and identify variations on file system and registry paths. Mostly it is working, but oddly the first item I test, no matter what it is, the properties are blank when I look, though no error shows up. And it seems like the results are actually from the previous instance, at least for the "successful" instances. Thrown exceptions seem to be working as expected. So, when $test = #('C:\folder', 'D:\', 'C:', 'K:', '\\Server\Folder\*', '\junk', '?:'), my results will be
C:\folder
D:\
FileSystem_Folder
LocalDisk folder
C:
FileSystem_Drive
OpticalDisk drive
K:
FileSystem_Drive
LocalDisk drive
\\Server\Folder\*
FileSystem_Drive
Unknown drive
! \junk
Not Identified: \junk
! ?:
Not Identified: ?:
This is the code in question.
class PxPath {
# Properties
hidden [array]$validPathTypes = #('FileSystem_Drive', 'FileSystem_Folder', 'FileSystem_File', 'Registry_Key', 'Registry_Property')
hidden [hashtable]$regExLookup = #{
registryHive = '^(?<hive>HKCC|HKEY_CURRENT_CONFIG|HKCR|HKEY_CLASSES_ROOT|HKCU|HKEY_CURRENT_USER|HKLM|HKEY_LOCAL_MACHINE)(?<seperator>:)?$'
registryPath = '^(?<hive>HKCC|HKEY_CURRENT_CONFIG|HKCR|HKEY_CLASSES_ROOT|HKCU|HKEY_CURRENT_USER|HKLM|HKEY_LOCAL_MACHINE)(?<seperator>:)?\\(?<remainder>.+)$'
fileSystemDriveOnly= '^(?<drive>[a-zA-Z]:)(\\)?$'
fileSystemDrivePath = '^(?<drive>[a-zA-Z]:)\\(?<remainder>.+)$'
fileSystemUNCPath = '^(?<server>\\\\[^<>:"/\\\|]*)\\(?<remainder>.+)$'
}
hidden [hashtable]$driveTypeLookup = #{
0 = 'Unknown'
1 = 'NoRootDirectory'
2 = 'RemovableDisk'
3 = 'LocalDisk'
4 = 'NetworkDrive'
5 = 'OpticalDisk'
6 = 'RamDisk'
}
[string]$Description = $null
[string]$Type = $null
# Constructors
PxPath ([string]$path) {
$this.PathType($path)
}
PxPath ([string]$path, [string]$pathType) {
if ($this.validPathTypes -contains $pathType) {
$this.PathType($path)
if ($this.Type -ne $pathType) {
Throw "$($this.Type) does not match: $pathType"
}
} else {
Throw "Not a valid path type: $pathType"
}
}
hidden [void] PathType ([string]$path) {
[string]$driveType = $null
[string]$extension = $null
[string]$extension = $null
switch -regex ($path) {
$this.regExLookup.fileSystemDriveOnly {
$driveType = $this.driveTypeLookup.Get_Item([int](Get-WmiObject Win32_LogicalDisk -computerName:. -filter:"name='$($matches.drive)'" | Select DriveType).DriveType)
$this.Description = "$driveType drive"
$this.Type = 'FileSystem_Drive'
}
$this.regExLookup.fileSystemDrivePath {
$driveType = $this.driveTypeLookup.Get_Item([int](Get-WmiObject Win32_LogicalDisk -computerName:. -filter:"name='$($matches.drive)'" | Select DriveType).DriveType)
if ($extension = [System.IO.Path]::GetExtension($path)) {
$this.Description = "$driveType file"
$this.Type = 'FileSystem_File'
} else {
$this.Description = "$driveType folder"
$this.Type = 'FileSystem_Folder'
}
}
$this.regExLookup.fileSystemUNCPath {
if ($extension = [System.IO.Path]::GetExtension($path)) {
$this.Description = "UNC file"
$this.Type = 'FileSystem_File'
} else {
$this.Description = "UNC folder"
$this.Type = 'FileSystem_Folder'
}
}
$this.regExLookup.registryPath {
}
default {
Throw "Not Identified: $path"
}
}
}
}
CLS
$test = #('C:\folder', 'D:\', 'C:', 'K:', '\\Server\Folder\*', '\junk', '?:')
foreach ($testitem in $test) {
$path = try {
[PxPath]::New($testitem)
Write-Host $testitem
Write-Host " $($path.Type)"
Write-Host " $($path.Description)"
Write-Host
} catch {
Write-Host "! $testitem"
Write-Host " $($_.Exception.Message)"
Write-Host
}
}
I suspect the issue is something I just don't understand with classes, but I guess a bug in PowerShell that I need to work around could also be the culprit.
Related
I am working on a script logging solution for an install script that has many tasks and long processing times, and I am trying to address the issue of network dropout. I am also moving to a StreamWriter based approach vs my old Add-Content approach for performance reasons.
The problem I am having is that once the network drops out, the StreamWriter doesn't reconnect. So, first question is, CAN I reconnect, or is this a limitation of StreamWriter? The fact that the StreamWriter has a cache that can be Flushed makes me think I may be doing a bunch of work to recreate functionality that is already there.
And second, I am starting to think a simpler/better solution is simply to write the log to a local folder, so the log is always complete, then simply attempt to copy that to the network location for progress review. I had been thinking about implementing parallel logs, so a loss of the network would still leave the local copy complete. Curious if anyone else looks at this and says "Well, YEAH, dufus, obviously."
function Get-PxLogFile {
return $script:pxLogFile
}
function Set-PxLogFile {
param (
[string]$path
)
if (Test-Path $path) {
Remove-Item $path -force
}
[string]$script:pxLogFile= $path
}
function Get-PxLogWriter {
$logFile = Get-PxLogFile
if (-not $script:pxFileStream) {
$script:pxFileStream = New-Object io.fileStream $logFile, 'Append', 'Write', 'Read'
$script:pxlogWriter = New-Object io.streamWriter $script:pxFileStream
} elseif ($script:pxFileStream.name -ne $logFile) {
$script:pxFileStream = New-Object io.fileStream $logFile, 'Append', 'Write', 'Read'
$script:pxlogWriter = New-Object io.streamWriter $script:pxFileStream
}
return $script:pxlogWriter
}
function Dispose-PxLogFile {
$script:pxlogWriter.Dispose()
$script:pxFileStream.Dispose()
$script:pxFileStream = $null
$script:pxlogWriter = $null
}
# Shared
function Get-PxDeferredLog {
return ,$script:deferredLog # , keeps PS from unrolling a single item array into a string
}
function Set-PxDeferredLog {
param (
[collections.arrayList]$deferredLog
)
[collections.arrayList]$script:deferredLog = $deferredLog
}
function Get-PxDeferredLogTimes {
return ($script:deferredLogTimes -join ', ')
}
function Finalize-PxLogFile {
$logWriter = Get-PxLogWriter
if (($deferredLog = Get-PxDeferredLog).count -gt 0) {
$abandonTime = (Get-Date) + (New-TimeSpan -seconds:3)
$logWriter = Get-PxLogWriter
:lastChanceLogWindow do {
$deferredLog = Get-PxDeferredLog
:deferredItemsWrite do {
try {
$logWriter.WriteLine($deferredLog[0])
if ($deferredLog.count -gt 0) {
$deferredLog.RemoveAt(0)
} else {
break :lastChanceWindow
}
} catch {
break :deferredItemsWrite
}
} while ($deferredLog.count -gt 0)
Set-PxDeferredLog $deferredLog
if ($deferredLog.count -eq 0) {
break :lastChanceWindow
}
if ((Get-Date) -gt $abandonTime) {
break :lastChanceLogWindow
}
} while ((Get-Date) -lt $abandonTime)
if ($deferredLog) {
Write-Host "Failed to write all log entries"
}
}
$script:deferredLogItems = $script:deferredLogTimes = $null
}
function Add-PxLogFileContent {
param (
[string]$string
)
$logWriter = Get-PxLogWriter
# Nested Functions
function Add-PxDeferredLogItem {
param (
[string]$item
)
if (-not $script:deferredLog) {
[collections.arrayList]$script:deferredLog = New-Object collections.arrayList
}
if ($script:deferredLog.count -eq 0) {
Start-PxDeferredLogTime
}
[void]$script:deferredLog.Add($item)
}
function Start-PxDeferredLogTime {
if (-not $script:deferredLogTimes) {
[collections.arrayList]$script:deferredLogTimes = New-Object collections.arrayList
}
if ((-not $script:deferredLogTimes) -or (-not $script:deferredLogTimes[-1].EndsWith('-'))) {
[void]$script:deferredLogTimes.Add("$((Get-Date).ToString('T'))-")
}
}
function Stop-PxDeferredLogTime {
if ($script:deferredLogTimes[-1].EndsWith('-')) {
$script:deferredLogTimes[-1] = "$($script:deferredLogTimes[-1])$((Get-Date).ToString('T'))"
}
}
$deferredLogProcessed = $false
if ([collections.arrayList]$deferredLog = Get-PxDeferredLog) {
$deferredLogProcessed = $true
:deferredItemsWrite do {
try {
$logWriter.WriteLine($deferredLog[0])
$logWriter.Flush()
$deferredLog.RemoveAt(0)
} catch {
break :deferredItemsWrite
}
} while ($deferredLog.count -gt 0)
if ($deferredLog.count -eq 0) {
$deferredLogPending = $false
} else {
$deferredLogPending = $true
}
Set-PxDeferredLog $deferredLog
} else {
$deferredLogPending = $false
}
if (-not $deferredLogPending) {
try {
if ($logWriter.WriteLine($string)) {
$logWriter.Flush()
}
if ($deferredLogProcessed) {Stop-PxDeferredLogTime}
} catch {
Add-PxDeferredLogItem $string
Write-Host "Failed: $(Get-Date)`n$($_.Exception.Message)"
}
} else {
Add-PxDeferredLogItem $string
}
}
### MAIN
Clear-Host
$script:deferredLogItems = $script:deferredLogTimes = $null
$logPath = '\\px\Content'
Write-Host 'logTest1.txt'
$startTime = Get-Date
$endTime = $startTime + (New-TimeSpan -minutes:5)
#Set-PxLogFile "$([System.IO.Path]::GetFullPath($env:TEMP))\logTest1.txt"
Set-PxLogFile "$logPath\logTest1.txt"
do {
Add-PxLogFileContent "logged: $(Get-Date)"
Start-SLeep -s:10
} while ((Get-Date) -lt $endTime)
Finalize-PxLogFile
Write-Host 'logTest2.txt'
$startTime = Get-Date
$endTime = $startTime + (New-TimeSpan -minutes:5)
#Set-PxLogFile "$([System.IO.Path]::GetFullPath($env:TEMP))\logTest2.txt"
Set-PxLogFile "$logPath\logTest2.txt"
do {
Add-PxLogFileContent "logged: $(Get-Date)"
Start-SLeep -s:10
} while ((Get-Date) -lt $endTime)
if ([string]$deferredLogTimes = Get-PxDeferredLogTimes) {
Add-PxLogFileContent "Deferred logging time ranges: $deferredLogTimes"
}
Finalize-PxLogFile
Dispose-PxLogFile
I have a third party function which creates a profile for a server. When I create an array and assign to hash that is required by third party function it is working fine, but when I dynamically create an array and assign it I am getting error.
Have tried working with simple variable that has all values and have created and array also of these values statically. But when I dynamically create it fails.
Function New-Disk
{
Param (
[parameter(Mandatory = $false)]
[Array] $XXX_drivedata
)
if ($XXX_drivedata[3] -ieq "yes")
{
$boot_data = $TRUE;
}
else
{
$boot_data = $FALSE;
}
if ($XXX_drivedata[9] -ieq "yes")
{
$erase_data = $TRUE;
}
else
{
$erase_data = $FALSE;
}
$params1 = #{
name = $XXX_drivedata[0];
RAID = $XXX_drivedata[1];
numberofDrives = $XXX_drivedata[2];
driveType = $XXX_drivedata[5];
driveSelectionBy = $XXX_drivedata[6];
minDriveSize = $XXX_drivedata[7];
maxDriveSize = $XXX_drivedata[8];
eraseDataOnDelete = $erase_data;
bootable = $boot_data;
accelerator = $XXX_drivedata[4];
storageLocation = $XXX_drivedata[10]
}
$params = $params1.Clone()
foreach($item in $params1.GetEnumerator())
{
#if ([string]::IsNullOrWhiteSpace($item.Value) -or ($item.Value -ieq "null"))
if (!$item.value)
{
$params.Remove($item.Key)
}
}
try {
$logical_disk_create = New-<Function for disk> #params
if ($logical_disk_create)
{
$XXX_disk_create_status = "pass"
return $SCID_disk_create_status,$logical_disk_create.SasLogicalJBOD
}
}
catch {
Write-Error $_
$XXX_disk_create_status = "fail"
return $XXX_disk_create_status,$logical_disk_create
continue
}
}
#------------------------------------------------
#Attach local disk and JBOD to controller
#------------------------------------------------
Function New-Controller
{
Param (
[parameter(Mandatory = $true)]
[Array] $SCID_controller_detail,
[parameter(Mandatory = $true)]
[Array] $SCID_logicaldisk_detail
)
if ($SCID_controller_detail[1] -ieq "yes")
{
$initialize_data = $TRUE;
}
else
{
$initialize_data = $FALSE;
}
$params1 = #{controllerID = $XXX_controller_detail[0];initialize = $initialize_data;writeCache = $XXX_controller_detail[2];logicalDisk = $XXX_logicaldisk_detail}
$params = $params1.Clone()
foreach($item in $params1.GetEnumerator())
{
if ($item.key -ne "logicalDisk")
{
#if ([string]::IsNullOrWhiteSpace($item.Value))
if (!$item.value)
{
$params.Remove($item.Key)
}
}
}
try {
$logicaldisk_controller_create = New-<Function for disk controller> #params
if ($logicaldisk_controller_create)
{
$SCID_disk_create_status = "pass"
return $SCID_disk_create_status,$logicaldisk_controller_create
}
}
catch {
Write-Error $_
$SCID_disk_create_status = "fail"
return $SCID_disk_create_status,$logicaldisk_controller_create
continue
}
}
#--------------------------------------------------
#Create Server Profile
#--------------------------------------------------
Function New-ServerProfile
{
.......
#------------------------------------------------------
#Read local disk and JBOD details
#------------------------------------------------------
$SP_logical_disk_list = #()
$SP_logical_disk_list_controller = #()
$XXX_controllerdata = #("$($serverprofile.localStorages.integratedStorageController.controllerID)", "$($serverprofile.localStorages.integratedStorageController.reinitialize)", "$($serverprofile.localStorages.integratedStorageController.writeCache)")
if ($serverprofile.localStorages.integratedStorageController.logicalDrive)
{
foreach ($logicaldrive in $($serverprofile.localStorages.integratedStorageController.logicalDrive))
{
$XXX_drivedata = #("$($logicaldrive.name)", "$($logicaldrive.raidLevel)", "$($logicaldrive.physicalDrives)", "$($logicaldrive.boot)", "$($logicaldrive.accelarator)", "$($logicaldrive.driveTechnology)")
$logicaldisk_create = New-Disk -XXX_drivedata $XXX_drivedata
if ($logicaldisk_create[0] -ne "fail")
{
$SP_logical_disk_list += $logicaldisk_create[1]
$XXX_drivedata.Clear()
}
}
$logdisk_controller = New-Controller -XXX_controller_detail $SCID_controllerdata -XXX_logicaldisk_detail $SP_logical_disk_list
if ($logdisk_controller[0] -ne "fail")
{
$SP_logical_disk_list_controller += $logdisk_controller[1]
}
}
...........................
$LogicalDisk = New-<Fuctionfordisk> -Name "MyDisk" | New-<Function for disk controller> -Initialize
$LogicalDisks = New-<Fuctionfordisk> -Name "MyDisk" | New-<Function for disk controller> -Initialize
$logcaldr = #($LogicalDisk, $LogicalDisks)
$params1 = #{
....................
other parameters
logicalDisk = $SP_logical_disk_list_controller
}
$params = $params1.Clone()
foreach($item in $params1.GetEnumerator())
{
if ($item.key -ne "LogicalDisk|localStorage")
{
#if ([string]::IsNullOrWhiteSpace($item.Value) -or ($item.Value -ieq "null"))
if (!$item.value)
{
$params.Remove($item.Key)
}
}
}
$task = New-<Server Profile> #params | Wait-<task>
When I use localdr it is working fine but when I use $SP_logical_disk_list_controller for Storage I am getting error
The JSON sent in the request contained a unknown type where a different unknown type was required on line 1 near column 746. Correct the content of the JSON and try the request again.
I have even tried using $logdisk_controller[1] but still same error comes.
I don't know much about PowerShell but have inherited a script from someone who is no longer available for assistance. This script imports AD Group Info and memberships related to Users and Computers. It works fine when run on a machine with PS 2.0 but it crashes if executed on PS 3.0 or newer.
I have not been able to figure out what needs to be modified but it seems the errors start occurring in the "Computer" membership import step and there are hundreds of errors that all say:
Command failed while processing computers: , Exception of type 'System.OutOfMemoryException' was thrown
Then at some point it looks like the script just stops and it never even gets to the 3rd step / function.
Any advice?
[Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices") | Out-Null
$DBServer = "DBSERVER"
$DBName = "DBNAME"
$TableUsers = "[$DBName].[dbo].[AD_GroupToClient]"
$TableComps = "[$DBName].[dbo].[AD_GroupToDevice]"
$TableGroups = "[$DBName].[dbo].[AD_Group_Info]"
$sqldateformat = "yyyy/MM/dd HH:mm:ss:fff"
[system.Data.SqlClient.SqlConnection]$global:SqlConnection = $null
function Get-ScriptPath { $Invocation = (Get-Variable MyInvocation -Scope 1).Value; Split-Path $Invocation.MyCommand.Path }
$ScriptPath = Get-ScriptPath
$Logfile = "$ScriptPath\OutLog.log"
function Write-Logfile {
param($logtext)
[string](Get-Date -format $sqldateformat) + "`t$logtext" | Out-File $Logfile -Encoding ascii -Append
}
function Open-Database {
$global:SqlConnection = New-Object system.Data.SqlClient.SqlConnection
try {
$global:SqlConnection.ConnectionString = "Server=$DBServer;Database=$DBName;Integrated Security=True"
$global:SqlConnection.Open() | Out-Null
Write-Logfile "OK`tDatabase opened"
} catch {
Write-Host "Error Opening SQL Database`t$($_.Exception.Message)"
Write-Logfile "Error`tDatabase open failed, $($_.exception.message)"
exit
}
}
function Close-Database {
$global:SqlConnection.Close()
Write-Logfile "OK`tDatabase closed"
}
function Esc-Quote {
param($str)
if ($str) { $str.Replace("'","''") }
}
function Run-DBCommand {
param($SqlCommands, [switch]$getnumrows)
if ($SqlCommands.Count -ge 1) {
$SqlCommandText = [string]::Join(";", $SqlCommands)
try {
$SqlCmd = New-Object Data.SqlClient.SqlCommand($SqlCommandText, $SqlConnection)
$returnvalue = $SqlCmd.ExecuteNonQuery()
if ($getnumrows) { return $returnvalue }
} catch {
Write-Logfile "Error`tSQL Command failed, $($_.exception.message)"
}
}
}
function Run-GroupMemberExport {
param($exportmode)
switch ($exportmode) {
"users" {
$dom = [ADSI]"LDAP://OU=Clients123,DC=test1,DC=test2,DC=test3"
$query = "(&(objectClass=user)(objectCategory=person)(samaccountname=*))"
$table = $TableUsers
$namecolumn = "AD_Group_Member_Name"
$attribs = #("samaccountname")
}
"computers" {
$dom = [ADSI]"LDAP://DC=test1,DC=test2,DC=test3"
$query = "(&(objectClass=computer)(samaccountname=*))"
$table = $TableComps
$namecolumn = "AD_Group_Member_Device"
$attribs = #("samaccountname", "whencreated")
}
}
$starttime = (Get-Date).ToUniversalTime().ToString($sqldateformat)
$srch = New-Object DirectoryServices.DirectorySearcher($dom, $query, $attribs)
$srch.PageSize = 1000
$srch.Sort = New-Object DirectoryServices.SortOption("sAMAccountName", [DirectoryServices.SortDirection]::Ascending)
$results = $srch.FindAll()
$count = 0
$numaccounts = $results.Count
foreach ($res in $results) {
try {
$objAccount = $res.GetDirectoryEntry()
$samaccountname = $objAccount.properties["samaccountname"][0]
$whencreated = ""
if ($exportmode -eq "computers") { $whencreated = Get-Date ([datetime]$objAccount.properties["whencreated"][0]) -Format $sqldateformat }
$count++
Write-Progress "Querying accounts" $samaccountname -PercentComplete ($count * 100.0 / $numaccounts)
$objAccount.psbase.RefreshCache("tokenGroups")
$SIDs = $objAccount.psbase.Properties.Item("tokenGroups")
$groups = #()
ForEach ($Value In $SIDs) {
$SID = New-Object System.Security.Principal.SecurityIdentifier $Value, 0
try {
$Group = $SID.Translate([System.Security.Principal.NTAccount]).Value
} catch {
$Group = $SID.Translate([System.Security.Principal.SecurityIdentifier]).Value
}
if ($groups -notcontains $Group -and $Group.Split("\")[1] -ne $samaccountname) { $groups += $Group }
}
Run-DBCommand #("DELETE FROM $table WHERE [$namecolumn] = '$(Esc-Quote $samaccountname)'")
$sqlcommands = #()
$currenttime = (Get-Date).ToUniversalTime().ToString($sqldateformat)
if ($groups) {
$groups | sort | foreach {
if ($exportmode -eq "users") {
$sqlcommands += "INSERT INTO $table ([$namecolumn], [AD_Group_Name], [Last_Update]) VALUES ('$(Esc-Quote $samaccountname)', '$(Esc-Quote $_)', '$currenttime')"
} else {
$sqlcommands += "INSERT INTO $table ([$namecolumn], [AD_Group_Name], [Last_Update], [Record_Created]) VALUES ('$(Esc-Quote $samaccountname)', '$(Esc-Quote $_)', '$currenttime', '$whencreated')"
}
if ($sqlcommands.count -ge 50) { Run-DBCommand $sqlcommands; $sqlcommands = #() }
}
} else {
if ($exportmode -eq "users") {
$sqlcommands += "INSERT INTO $table ([$namecolumn], [AD_Group_Name], [Last_Update]) VALUES ('$(Esc-Quote $samaccountname)', 'ERROR: Unable to retrieve groups', '$currenttime')"
} else {
$sqlcommands += "INSERT INTO $table ([$namecolumn], [AD_Group_Name], [Last_Update], [Record_Created]) VALUES ('$(Esc-Quote $samaccountname)', 'ERROR: Unable to retrieve groups', '$currenttime', '$whencreated')"
}
}
Run-DBCommand $sqlcommands
} catch {
Write-Logfile "Error`tCommand failed while processing $exportmode`: $($objAccount.name), $($_.exception.message)"
}
}
Write-Progress " " " " -Completed
if ($count -eq $numaccounts) {
$numdeleted = Run-DBCommand #("DELETE FROM $table WHERE [Last_Update] < '$starttime' OR [Last_Update] IS NULL") -getnumrows
Write-Logfile "OK`tUpdates for $exportmode completed, $numdeleted old records deleted."
}
}
function Run-GroupDescriptionExport {
$dom = [ADSI]"LDAP://DC=test1,DC=test2,DC=test3"
$query = "(&(objectClass=group)(samaccountname=*))"
$table = $TableGroups
$attribs = #("samaccountname", "displayname", "description", "whencreated", "managedby", "grouptype","distinguishedname","whenchanged")
$srch = New-Object DirectoryServices.DirectorySearcher($dom, $query, $attribs)
$srch.PageSize = 1000
$srch.Sort = New-Object DirectoryServices.SortOption("sAMAccountName", [DirectoryServices.SortDirection]::Ascending)
$results = $srch.FindAll()
$count = 0
$numgroups = $results.Count
$sqlcommands = #()
$starttime = [datetime]::Now.ToUniversalTime().ToString($sqldateformat)
foreach ($res in $results) {
$count++
$samaccountname = $res.properties["samaccountname"][0]
Write-Progress "Querying accounts, $count/$numgroups" $samaccountname -PercentComplete ($count * 100.0 / $numgroups)
$displayName = ""; if ($res.properties.contains("displayname")) { $displayName = $res.properties["displayname"][0] }
$description = ""; if ($res.properties.contains("description")) { $description = $res.properties["description"][0] }
$managedby = ""; if ($res.properties.contains("managedby")) { $managedby = $res.properties["managedby"][0] }
$grouptype = ""; if ($res.properties.contains("grouptype")) { $grouptype = $res.properties["grouptype"][0] }
$distinguishedname = ""; if ($res.properties.contains("distinguishedname")) { $distinguishedname = $res.properties["distinguishedname"][0] }
$whencreated = ""; if ($res.properties.contains("whencreated")) { $whencreated = ([datetime]$res.properties["whencreated"][0]).ToString($sqldateformat) }
$whenchanged = ""; if ($res.properties.contains("whenchanged")) { $whenchanged = ([datetime]$res.properties["whenchanged"][0]).ToString($sqldateformat) }
$lastupdated = [datetime]::Now.ToUniversalTime().ToString($sqldateformat)
$sqlcommand = "DELETE FROM $table WHERE [AD_Group_Name] = '$(Esc-Quote $samaccountname)'; "
$sqlcommand += "INSERT INTO $table ([AD_Group_Name], [AD_Group_DisplayName], [AD_Group_Description], [Last_Update], [Managed_By],[Distinguished_Name],[Group_Category],[Created_On], AD_Last_Modified]) VALUES ('$(Esc-Quote $samaccountname)', '$(Esc-Quote $displayName)', '$(Esc-Quote $description)', '$lastupdated', '$(Esc-Quote $managedby)', '$(Esc-Quote $distinguishedname)', '$grouptype', '$whencreated','$whenchanged')"
$sqlcommands += $sqlcommand
if ($sqlcommands.count -ge 100) { Run-DBCommand $sqlcommands; $sqlcommands = #()
}
}
Run-DBCommand $sqlcommands
if ($numgroups -eq $count) {
Run-DBCommand #("DELETE FROM $table WHERE [Last_Update] <= '$starttime'")
}
Write-Progress " " " " -Completed
}
Open-Database
Run-GroupMemberExport "users"
Run-GroupMemberExport "computers"
Run-GroupDescriptionExport
Close-Database
This doesn't have anything to do with the PowerShell version. You're just plain running out of memory. You're pulling in a lot of data, so you need to be more conscious of getting rid of that data when you're done with it.
There are a couple things you can do to clean up memory:
First, the documentation for DirectorySearcher.FindAll() says:
Due to implementation restrictions, the SearchResultCollection class cannot release all of its unmanaged resources when it is garbage collected. To prevent a memory leak, you must call the Dispose method when the SearchResultCollection object is no longer needed.
So whenever you do:
$results = $srch.FindAll()
Make sure you call $results.Dispose() when you're done with it (at the end of the function).
Second, when you loop through the results in your Run-GroupMemberExport function, you're calling $res.GetDirectoryEntry(). Usually you can just let the garbage collector clean up DirectoryEntry objects, but when you're creating so many in a loop like that, the GC doesn't have time to run. This has happened to me when I've run a loop over thousands of accounts.
To solve this, you can call Dispose() on the DirectoryEntry objects yourself. Since you already have a try/catch block there, I would suggest adding a finally block to make sure it happens even if an error is thrown:
try {
...
} catch {
Write-Logfile "Error`tCommand failed while processing $exportmode`: $($objAccount.name), $($_.exception.message)"
} finally {
$objAccount.Dispose()
}
Actually, you could probably just not use GetDirectoryEntry() at all. Just ask the DirectorySearcher to return the other attributes you need. But if you want to still use it, then make sure you call RefreshCache for every attribute you need (you can put them all in one call to RefreshCache). If you access the Properties collection and ask for a value that it does not already have in cache, then it will ask AD for every attribute with a value - that's a lot of unnecessary data.
I've been trying to compare two hashes of hashes against each other. Unfortunately even with the great help from here I struggled to pull together what I was trying to do.
So, I've resorted to searching the internet again and I was lucky enough to find Edxi's code (full credit to him and Dbroeglin who looks to have written it originally)https://gist.github.com/edxi/87cb8a550b43ec90e4a89d2e69324806 His compare-hash function does exactly what I needed in terms of its comparisons. However, I'd like to report of the full path of the hash in the final output. So I've tried to update the code to allow for this. My thinking being that I should be able to take the Key (aka path) while the code loops over itself but I'm clearly going about it in the wrong manner.
Am I going about this with the right approach, and if not, would someone suggest another method please? The biggest problem I've found so far is that if I change the hash, my code changes don't work e.g. adding 'More' = 'stuff' So it becomes $sailings.Arrivals.PDH083.More breaks the path in the way I've set it.
My version of the code:
$Sailings = #{
'Arrivals' = #{
'PDH083' = #{
'GoingTo' = 'Port1'
'Scheduled' = '09:05'
'Expected' = '10:11'
'Status' = 'Delayed'
}
}
'Departures' = #{
'PDH083' = #{
'ArrivingFrom' = 'Port1'
'Scheduled' = '09:05'
'Expected' = '09:05'
'Status' = 'OnTime'
'Other' = #{'Data' = 'Test'}
}
}
}
$Flights = #{
'Arrivals' = #{
'PDH083' = #{
'GoingTo' = 'Port'
'Scheduled' = '09:05'
'Expected' = '10:20'
'Status' = 'Delayed'
}
}
'Departures' = #{
'PDH083' = #{
'ArrivingFrom' = 'Port11'
'Scheduled' = '09:05'
'Expected' = '09:05'
'Status' = 'NotOnTime'
'Other' = #{'Data' = 'Test_Diff'}
}
}
}
function Compare-Hashtable {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[Hashtable]$Left,
[Parameter(Mandatory = $true)]
[Hashtable]$Right,
[string] $path,
[boolean] $trackpath = $True
)
write-host "PAth received as: $path"
function New-Result($Key, $LValue, $Side, $RValue) {
New-Object -Type PSObject -Property #{
key = $Key
lvalue = $LValue
rvalue = $RValue
side = $Side
}
}
[Object[]]$Results = $Left.Keys | ForEach-Object {
if ($trackpath ) {
write-host "Working on Path: " $path + $_
}
if ($Left.ContainsKey($_) -and !$Right.ContainsKey($_)) {
write-host "Right key not matched. Report path as: $path"
New-Result $path $Left[$_] "<=" $Null
}
else {
if ($Left[$_] -is [hashtable] -and $Right[$_] -is [hashtable] ) {
if ($path -ne $null -or $path -ne "/") {
$path = $path + "/" + $_
write-host "Sending Path to function as: $path"
}
Compare-Hashtable $Left[$_] $Right[$_] $path
}
else {
$LValue, $RValue = $Left[$_], $Right[$_]
if ($LValue -ne $RValue) {
$path = $path + "/" + $_
write-host "Not a hash so must be a value at path:$path"
New-Result $path $LValue "!=" $RValue
}
else {
Write-Host "Before changing path: $path "
if (($path.Substring(0, $path.lastIndexOf('/'))).length >0) {
$path = $path.Substring(0, $path.lastIndexOf('/'))
Write-Host "After changing path: $path "
}
}
}
}
# if (($path.Substring(0, $path.lastIndexOf('/'))).length >0) {
# Tried to use this to stop error on substring being less than zero
# but clearly doesnt work when you add more levels to the hash
$path = $path.Substring(0, $path.lastIndexOf('/'))
# } else { $path = $path.Substring(0, $path.lastIndexOf('/')) }
}
$Results += $Right.Keys | ForEach-Object {
if (!$Left.ContainsKey($_) -and $Right.ContainsKey($_)) {
New-Result $_ $Null "=>" $Right[$_]
}
}
if ($Results -ne $null) { $Results }
}
cls
Compare-Hashtable $Sailings $Flights
outputs
key lvalue side rvalue
--- ------ ---- ------
/Arrivals/PDH083/Expected 10:11 != 10:20
/Arrivals/GoingTo Port1 != Port
/Departures/PDH083/ArrivingFrom Port1 != Port11
/Departures/PDH083/Status OnTime != NotOnTime
/Departures/PDH083/Other/Data Test != Test_Diff
I won't do that much string manipulation on the $Path but threat $Path as an array and join it to a string at the moment you assign it as a property (key = $Path -Join "/") to the object:
function Compare-Hashtable {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[Hashtable]$Left,
[Parameter(Mandatory = $true)]
[Hashtable]$Right,
[string[]] $path = #(),
[boolean] $trackpath = $True
)
write-host "Path received as: $path"
function New-Result($Path, $LValue, $Side, $RValue) {
New-Object -Type PSObject -Property #{
key = $Path -Join "/"
lvalue = $LValue
rvalue = $RValue
side = $Side
}
}
$Left.Keys | ForEach-Object {
$NewPath = $Path + $_
if ($trackpath ) {
write-host "Working on Path: " $NewPath
}
if ($Left.ContainsKey($_) -and !$Right.ContainsKey($_)) {
write-host "Right key not matched. Report path as: $NewPath"
New-Result $NewPath $Left[$_] "<=" $Null
}
else {
if ($Left[$_] -is [hashtable] -and $Right[$_] -is [hashtable] ) {
Compare-Hashtable $Left[$_] $Right[$_] $NewPath
}
else {
$LValue, $RValue = $Left[$_], $Right[$_]
if ($LValue -ne $RValue) {
New-Result $NewPath $LValue "!=" $RValue
}
}
}
}
$Right.Keys | ForEach-Object {
$NewPath = $Path + $_
if (!$Left.ContainsKey($_) -and $Right.ContainsKey($_)) {
New-Result $NewPath $Null "=>" $Right[$_]
}
}
}
cls
Compare-Hashtable $Sailings $Flights
side-note: Write Single Records to the Pipeline (SC03), see: Strongly Encouraged Development Guidelines. In other words, don't do the $Results += ... but intermediately put any completed result in the pipeline for the next cmdlet to be picked up (besides, it unnecessarily code)...
I have created an app for back up and restore of computers. I also allows modification of ADObjects through the use of custom Profile.ps1 file. The app runs fine in the ISE with no errors and works properly no errors in Windows 7. However, when I try to run it in a newly imaged Windows 10 machine I get "Property Can Not Be Found" errors on all my object properties.
The thing is I can read all the properties when I fill comboboxes fine. The error only occurs when the form is submitted. I will attach 1 of the forms I am having a problem with. Again it runs fine in Windows 7, but not Windows 10.
Could this be a problem with Microsoft updates?
Also, yes, I am setting Set-ExecutionPolicy -Scope Process -ExecutionPolicy Unrestricted.
Error message:
The property 'company' cannot be found on this object. Verify that the
property exist and can be set.
+ $CO.company = $company
+ Categoryinfo :InvalidOperation: (:) [] RuntimeExeption
Code:
. \\iotsdsp01pw\installs$\MoveToOU\PcDeployment\Profile.ps1
#region Validation Functions
function Validate-IsEmail ([string]$Email) {
return $Email -match "^(?("")("".+?""#)|(([0-9a-zA-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-zA-Z])#))(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,6}))$"
}
function Validate-IsURL ([string]$Url) {
if ($Url -eq $null) {
return $false
}
return $Url -match "^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_]*)?$"
}
function Validate-IsName ([string]$Name, [int]$MaxLength) {
if ($MaxLength -eq $null -or $MaxLength -le 0) {
#Set default length to 40
$MaxLength = 40
}
return $Name -match "^[a-zA-Z''-'\s]{1,$MaxLength}$"
}
function Validate-IsIP ([string]$IP) {
return $IP -match "\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b"
}
function Validate-IsEmptyTrim ([string]$Text) {
if ($text -eq $null -or $text.Trim().Length -eq 0) {
return $true
}
return $false
}
function Validate-IsEmpty ([string]$Text) {
return [string]::IsNullOrEmpty($Text)
}
function Validate-IsDate ([string]$Date) {
return [DateTime]::TryParse($Date, [ref](New-Object System.DateTime))
}
#endregion
$No_Load = {
$NewForm.Close()
#Initialize variables
$dateTime = Get-Date -Format G
$userName = (Get-WmiObject -Class Win32_ComputerSystem -Property UserName).UserName
$computerName = $env:computername
#Varables for display
$distinguishedName = (Get-dn computer cn $computerName)
$computerObject = (Get-ADObject $distinguishedName)
$organizationalUnit = (Get-ADObject "OU=Agencies, DC=state, DC=in, DC=us")
#Initialize Form Controls
$lblUserNameNewNo.Text = $userName
$lblComputerNameNewNo.Text = $computerName
$lblPhysicalLocationNewNo.Text = $computerObject.location
$txtBillingCodeNewNo.Text = $computerObject.departmentNumber
$comboboxAccountTypeNewNo.Text = $computerObject.extensionAttribute15
$comboboxOrganizationalUnitNewNo.Text = $computerObject.company
Load-ComboBox -ComboBox $comboboxOrganizationalUnitNewNo ($organizationalUnit.children | %{ $_.OU })
}
#region Control Helper Functions
function Load-ComboBox {
Param (
[ValidateNotNull()]
[Parameter(Mandatory = $true)]
[System.Windows.Forms.ComboBox]$ComboBox,
[ValidateNotNull()]
[Parameter(Mandatory = $true)]
$Items,
[Parameter(Mandatory = $false)]
[string]$DisplayMember,
[switch]$Append
)
if (-not $Append) {
$ComboBox.Items.Clear()
}
if ($Items -is [Object[]]) {
$ComboBox.Items.AddRange($Items)
} elseif ($Items -is [Array]) {
$ComboBox.BeginUpdate()
foreach ($obj in $Items) {
$ComboBox.Items.Add($obj)
}
$ComboBox.EndUpdate()
} else {
$ComboBox.Items.Add($Items)
}
$ComboBox.DisplayMember = $DisplayMember
}
#Validation
function ParameterValidate {
Param (
[Parameter(Mandatory = $true)]
[ValidateNotNull()]
[ValidateLength(1, 10)]
[String]$Text
)
return $true
}
$comboboxOrganizationalUnitNewNo_Validating = [System.ComponentModel.CancelEventHandler]{
#Check if the Name field is empty
$result = Validate-IsEmptyTrim $comboboxOrganizationalUnitNewNo.Text
if ($result -eq $true) {
#Mark a failure only if the Validation failed
$script:ValidationFailed = $true
#Display an error message
$errorprovider1.SetError($comboboxOrganizationalUnitNewNo, "Please select agency.");
} else {
#Clear the error message
$errorprovider1.SetError($comboboxOrganizationalUnitNewNo, "");
}
}
$txtBillingCodeNewNo_Validating = [System.ComponentModel.CancelEventHandler]{
#Check if the Name field is empty
$result = Validate-IsEmptyTrim $txtBillingCodeNewNo.Text
if ($result -eq $true) {
#Mark a failure only if the Validation failed
$script:ValidationFailed = $true
#Display an error message
$errorprovider1.SetError($txtBillingCodeNewNo, "Please enter billing code.");
} else {
#Clear the error message
$errorprovider1.SetError($txtBillingCodeNewNo, "");
}
}
$comboboxAccountTypeNewNo_Validating = [System.ComponentModel.CancelEventHandler]{
$result = Validate-IsEmptyTrim $comboboxAccountTypeNewNo.Text
if ($result -eq $true) {
#Mark a failure only if the Validation failed
$script:ValidationFailed = $true
#Display an error message
$errorprovider1.SetError($comboboxAccountTypeNewNo, "Please enter agency type.");
} else {
#Clear the error message
$errorprovider1.SetError($comboboxAccountTypeNewNo, "");
}
}
$control_Validated = {
#Pass the calling control and clear error message
$errorprovider1.SetError($this, "");
}
$No_FormClosing = [System.Windows.Forms.FormClosingEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.FormClosingEventArgs]
#Validate only on OK Button
if ($No.DialogResult -eq "OK") {
#Init the Validation Failed Variable
$script:ValidationFailed = $false
#Validate the Child Control and Cancel if any fail
$No.ValidateChildren()
#Cancel if Validation Failed
$_.Cancel = $script:ValidationFailed
}
}
#Events
$buttonColor_Click = {
#TODO: Place custom script here
$colordialog1.ShowDialog()
}
$linklblViewListNewNo_LinkClicked = [System.Windows.Forms.LinkLabelLinkClickedEventHandler]{
Start-Process "http://billingcodes.iot/"
}
$btnSubmitNewNo_Click = {
#TODO: Place custom script here
$company = $comboboxOrganizationalUnitNewNo.Text
$departmentNumber = $txtBillingCodeNewNo.Text
$accountType = $comboboxAccountTypeNewNo.Text
if ($accountType -eq "Seat") {
$accountType = " "
}
#Varables for Set-ADObject
$computerObject.company = $company
$computerObject.departmentNumber = $departmentNumber
$computerObject.extensionAttribute15 = $accountType
try {
$computerObject.SetInfo()
[Environment]::Exit(1)
} catch {
$labelDialogRedNewNo.Text = "AD computer object not found"
}
}
This is your culprit (from what I can see):
$No_Load = {
...
$computerObject = (Get-ADObject $distinguishedName)
...
}
...
$btnSubmitNewNo_Click = {
...
$computerObject.company = $company
...
}
You assign a computer object to the variable $computerObject in one scriptblock, and try to change one of its properties in another scriptblock. However, to be able to share a variable between scriptblocks you need to make it a global variable, otherwise you have two separate local variables that know nothing about each other.
$No_Load = {
...
$global:computerObject = Get-ADObject $distinguishedName
...
}
...
$btnSubmitNewNo_Click = {
...
$global:computerObject.company = $company
...
}
BTW, I doubt that this ever worked in Windows 7, since it failed with the same error on my Windows 7 test box.