79583179

Date: 2025-04-20 08:38:35
Score: 1.5
Natty:
Report link

Preamble

The distribution of print queues to users by group policies is called GPP and not GPO. The main difference between GPO and GPP is: if the applied GPO is no longer applied, the effect does not occur. This does not happen in GPP. If the applied GPP is no longer applied, the effect continues on the client that received it.

Someone might say: "but everyone calls it a GPO". This is a false statement. People who call GPP a GPO actually mean to call it a group policy, in a generic way. Many do not even know the difference between GPP and GPO. Since we believe that we are writing in a technical way, it is more appropriate to use the correct technical terms to designate certain situations and avoid possible ambiguities.

In the case mentioned by @dave-g, a created GPP was modified or deleted. It is necessary to remove the effects of the old GPP. A GPP for distributing print queues usually affects the user profile. So, to the best of my knowledge, the affected branch is HKCU.

So, how do you "act" on the HKCU branch in the context of the "System" account? In theory, the script to modify the HKCU branch needs to be executed in the context of the account, and will only affect the profile of this account. If the account has administrative privileges, the script can act in the context of the machine (HKLM).

Some will say that the System account can act on the HKU branch and affect users. This will only be true for account profiles in memory, such as S-1-5-18, S-1-5-19, S-1-5-20, S-1-5-21-[User SID] and the profile without an interactive account (.DEFAULT). Profiles that have not been loaded will not be affected. It is possible to act on the branches of all profiles, even if they are not loaded. It is quite complex, so I will not cover it here in this post.

Conclusion: in general, a script will act on the HKLM branch in the context of the account, if it has administrative privileges, or if it is executed by the System account; it will act on the HKCU branch in the context of the account itself and not other profiles; it will act on the HKU branch if the account has administrative privileges, and only profiles loaded in memory will be affected.

1st Draft

The created function has some errors. I will try to explain.

Here is the only syntactical error in the draft presented param([type]$Parameter1 [,[type]$Parameter2])

  [string]$KeyName = "',,server-01,printer-01',',,server-01,printer-01'"
  [string]$RegistryRoot = "HKLM:, HKCU:"

Parameters must be separated by commas.

Another problem, which is not a syntactic, semantic or logical error. Although quotation marks serve as string delimiters, it is recommended to use an apostrophe when the string is invariable, that is, it does not have a variable between the delimiters. This helps the language interpreter not to analyze the content of the string during execution.

As already mentioned by @mathias-r-jessen, the variable can be a string array.

Here I believe it was the famous "copy and paste". The same value appears twice in the variable "server-01,printer-01".

This is a rare issue, but it could cause problems during execution. Imagine the command del c:*.exe. If the current folder of the c: drive is "\Windows\System32", the command will be executed in the current folder. To ensure that the reference is from the root of a drive, you should use ":\" and not ":".

So, one way to rewrite such lines would be:

  [string[]]$KeyName = @( ',,server-01,printer-01', ',,server-01,printer-02' ),
  [string[]]$RegistryRoot = 'HKLM:\', 'HKCU:\'

As already discussed by @mathias-r-jessen, there is no need for the Split method if the variable is already an array.

  $registryPaths = $RegistryRoot.Split(",")
  foreach ($root in $registryPaths) {

So, one way to rewrite such lines would be:

  $registryPaths |
    ForEach-Object {
      $root = $PSItem

Since the variable was a string, the like comparison is inverted, based on the available information. And the property should be PSChildName and not Name, since it is a registry item.

  Where-Object { $_.Name -like "*$KeyName*" } |

It should be, according to the draft proposal:

  Where-Object { $KeyName -like "*'$( $_.PSChildName )'*" } |

But since the variable should be an array, one way to rewrite the line would be:

  Where-Object { $PSItem.PSChildName -in $KeyName } |

And finally, there is one more logical error.

  Remove-Item -Path $_.FullName -Recurse -Force

In the registry item, as far as I know, there is no FullName property, but rather PSPath. One way to rewrite the line would be:

  Remove-Item -Path $PSItem.PSPath -Recurse -Force

2nd Draft

Using the Recurse parameter at the root of a drive may cause some slowness. In this specific case, the paths are known and defined.

A more efficient code to avoid recursion when searching for items with known paths would be:

  function Remove-RegistryKey {
    param(
      [string[]]$KeyName = @( ',,server-01,printer-01', ',,server-01,printer-02' ),
      [string[]]$KeyRoot = @( 'Registry::HKCU\Printers\Connections\*',
        'Registry::HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Providers\Client Side Rendering Print Provider*\Printers\Connections\*'
      )
    )
    Get-ChildItem -Path $KeyRoot -ErrorAction SilentlyContinue |
      Where-Object { $PSItem.PSChildName -in $KeyName } |
        ForEach-Object {
            try {
                Remove-Item -Path $_.FullName -Recurse -Force
                Write-Host "Successfully deleted registry key: $($_.FullName)"
            } catch {
                Write-Warning "Error deleting registry key $($_.FullName): $($_.Exception.Message)"
            }
        }
  }

Example usage:

  $KeyNameToDelete = ',,server-01,printer-01'
  Remove-RegistryKey -KeyName $KeyNameToDelete

Changing the way printer queues are created

In 1996, when I started working with Windows 95 and Windows NT support, I used "\\server\printer". With AD, we did not create GPP. In 2008, I learned that a local printer queue could have a TCPIP port, without the need for SMB sharing.

The work became much simpler. Before, each user had to capture the printer queue on each computer. With the local queue, the user already had the queue available to him, without needing to capture and without needing to create a print server.

The problem became distributing the printer driver, including the local TCPIP port and creating the queue. Obviously, if the printer is USB, this technique will not work. But we decided to remove the USB printers and put all of them with IP addresses.

I suggest you think about this scenario.

Users with open session (off topic)

As I wrote in the comment to @halfix, I'm adding the code here because the formatting would look really bad. I apologize for continuing the comment this way.

Finding the user with an interactive session is very simple.

  $InteractiveSession = ( Get-CimInstance -ClassName 'CIM_UnitaryComputerSystem' ).UserName

Finding the user with an open session using only PowerShell commands is more laborious because Get-Process does not return process ownership information. So all that's left is to use the GetOwner method for each process. I have an inventory, created for my own use, made only in PowerShell. I present an adapted code in this question.

  $OpenSession = @{}
  $ProcessesToAudit =
    'cmd.exe', 'conhost.exe',
    'pwsh.exe', 'powershell.exe',
    'explorer.exe'
  $DoNotFilterProcesses = $true
  Get-CimInstance -ClassName 'CIM_Process' |
    Where-Object -FilterScript {
      $DoNotFilterProcesses -or
      $PSItem.Name -in $ProcessesToAudit
    } |
    ForEach-Object -Process {
      $CurrentProcessId = $PSItem.ProcessId
      $CurrentProcessName = $PSItem.Name
      $CurrentProcess =
        Get-Process |
          Where-Object -FilterScript {
            $PSItem.Id -eq $CurrentProcessId
          }
        If ( $CurrentProcess ) {
          Invoke-CimMethod -InputObject $PSItem -MethodName 'GetOwner' |
            ForEach-Object -Process {
              $CurrentUserAccount = $PSItem.Domain + '\' + $PSItem.User
                If ( $OpenSession[ $CurrentUserAccount ].Count -eq 0 ) {
                  $OpenSession[ $CurrentUserAccount ] = @()
                }
                If ( $CurrentProcessName -in $ProcessesToAudit ) {
                  If ( $CurrentProcessName -notin $OpenSession[ $CurrentUserAccount ] ) {
                    $OpenSession[ $CurrentUserAccount ] += $CurrentProcessName
                  }
                } Else {
                  If ( 'various processes' -notin $OpenSession[ $CurrentUserAccount ] ) {
                    $OpenSession[ $CurrentUserAccount ] += 'various processes'
                  }
                }
            }
        }
    }

I used an online translator. I apologize for not being fluent in the language.

Reasons:
  • Blacklisted phrase (1): how do you
  • Long answer (-1):
  • Has code block (-0.5):
  • Contains question mark (0.5):
  • User mentioned (1): @dave-g
  • User mentioned (0): @mathias-r-jessen
  • User mentioned (0): @mathias-r-jessen
  • User mentioned (0): @halfix
  • Low reputation (0.5):
Posted by: João Mac