Backup GPOs basing on GPO DisplayName rather than Id - powershell

So I am backing up all the GPOs of a Domain Controller and I noticed that the way Backup-GPO cmdlet backs up the GPOS is so unfriendly. By default it creates a folder for every single GPO named after the "ID" which doesn't even match its "GPOID/GUID".
Here is an example, I will just backup a specific GPO:
backup-gpo -guid ff8de365-0842-46ab-9ac7-64ebd8dd4614 -path C:\
DisplayName : N12 Workstation Policy
GpoId : ff8de365-0842-46ab-9ac7-64ebd8dd4614
Id : dd33c220-bac8-4ebd-a9d9-7729fcea9c38
BackupDirectory : C:\
CreationTime : 20/10/2015 17:41:43
DomainName : martyn.local
This is the backup folder name that is created after issuing the previous command:
{DD33C220-BAC8-4EBD-A9D9-7729FCEA9C38}
If I try to Backup all the GPOs I get a folder for every single GPO. Is there any way of naming these folders basing on the GPO DisplayName rather than that unfriendly string?
This is what I would like to get:
N12 Workstation Policy
The reason why I want to do it like that is because if I want to re-import a single GPO in the future and I don't remember the name of the GPO how am I supposed to know which is the right GPO backup folder to import if I am using that awful name?
Thanks

Backup each GPO to separate subfolders of your backup folder:
$invalidChars = ':\\/' + [RegEx]::Escape(-join [IO.Path]::InvalidPathChars)
$backupDir = 'C:\backup'
Get-GPO -All | ForEach-Object {
$name = $_.DisplayName -replace "[$invalidChars]", '_'
$gpoDir = Join-Path $backupDir -ChildPath $name
New-Item $gpoDir -Type Directory | Out-Null
Backup-GPO -Guid $_.Id -Path $gpoDir
}
The replacement is to remove characters that are invalid in a path from the name (the creation of the subfolder would fail if the name of the GPO contained for instance a >). As #briantist suggested in his comment it's better to derive the list of invalid characters from the respective IO.Path property than maintain the list by hand. You may need to manually add other problematic characters, though, specifically :. The colon is used for accessing alternate data streams, so it's technically a valid character in a path, but PowerShell doesn't support it.

Related

is there a way to check if a program is installed just by filename in powershell?

I am trying to create a powershell script to auto install all .msi and .exe files silently in a directory. However while doing this i want to check if any of the programs are already installed.
I know i can get all installed files in the system like below
$32bit_softwares = Get-ItemProperty HKLM:\SOFTWARE\wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* |
Select-Object DisplayName,DisplayVersion,Publisher,InstallDate
$64bit_softwares = Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\* |
Select-Object DisplayName,DisplayVersion,Publisher,InstallDate
$all_softwares = $32bit_softwares+$64bit_softwares
and i can get the filenames of the files in the directory like below:
$directoryRead = Read-Host -Prompt "enter directory"
$fileNames = Get-ChildItem $directory -Recurse -include *.exe,*.msi | ForEach-Object {$_.name}
How can i compare these 2 in a loop? like
$all_softwares.DisplayName -like "$softwareName*"
I am not sure a like filter above will do the job as filenames will be like examplename.exe
Thanks in advance.
So the problem that I see and I think you are asking about is that the installer filename will be different than the software name you pull out of the Registry. With the difference it will be hard to match up exactly.
Is the set of MSI's and/or EXE's (the installers) a known, static set? Would it be possible to setup a hash table (dictionary) mapping between the Registry name and the installer name?
This would make matching exactly while looping through the installers and doing a .Contains on the array from the Registry easier.

Correct way to access the USERPROFILE dir of a specific user in a script?

I'm writing a script to perform some file operations in the USERPROFILE folder of each (local) user on a Windows machine.
I have found various examples that use $env:USERPROFILE to identify the profile directory of the current logged-in user. I have also seen examples that assume all user profiles are saved in C:\Users\ and iterate/filter over that folder.
However, profile folders can be moved on Windows. My aim is to find (robustly) the profile directory of a specific user, given either that user's username (string) or a LocalUser object.
I can get an array of User objects based on active accounts with-
$users = Get-LocalUser | Where-Object Enabled -eq true
But the properties of those LocalUser objects are limited, and the UserProfile path is not among them. I believe this information is stored in the registry. I've through the PowerShell docs multiple times, but I haven't yet found the correct incantation that will give me the path of a user profile for a given user, which I can use in a loop to iterate across all users and their profile folders.
You can retrieve the root (parent) directory of all user profile directories from the registry as follows:
$profilesRootDir =
Get-ItemPropertyValue 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' ProfilesDirectory
To get a specific user's profile directory, say jdoe, you can then use:
# See more robust alternative below.
Join-Path $profilesRootDir jdoe
However, the ultimate source of truth is the ProfileImagePath value in the subkeys of the above registry key path, named for each user's SID (security identifier), which Get-LocalUser does provide (the output objects have a .SID property).
Thus, it is better to use:
Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$((Get-LocalUser jdoe).SID)" ProfileImagePath
To reliably get the profile directories of all enabled local users, use the following:
Get-LocalUser |
Where-Object Enabled |
ForEach-Object {
# Note the use of ...\ProfileList\$($_.SID) and value name ProfileImagePath
Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$($_.SID)" ProfileImagePath
}
Perhaps something along this line:
$users = Get-LocalUser | Where-Object Enabled -eq true
$profilesRootDir = #(
Get-ItemPropertyValue 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' ProfilesDirectory)
ForEach ($User in $Users) {
$UserPath = Join-Path -Path "$profilesRootDir" -ChildPath "$User"
"User : $User`n" +
"User-Path: $UserPath"
}
Output:
User : Bruce
User-Path: C:\Users\Bruce
You can use the wmi class win32_userprofile, but it only has sid, not username:
get-wmiobject win32_userprofile | select sid,localpath
sid localpath
--- ---------
S-1-5-21-3961843708-1234567890-2901110831-1002 C:\Users\user

Pulling a dir and all its contents from every client on a domain and copy to file share?

I am looking to copy a bunch of dir's "C:\Users\userOne\AppData\Roaming\Microsoft\SystemCertificates\My\Certificates" and all of their contents onto a share with the folder being named the client and the user name.
I am sure a for loop is the best way to go about this but I am hung up on what variables to use to get what I want.
The array will surely contain a list of every workstation on the AD, but I need to specify a bunch of directories per workstation. Maybe a wildcard like this will work? C:\Users*\AppData\Roaming\Microsoft\SystemCertificates\My\Certificates
I've seen tons of useful examples on here but most of them involve copying from one location to many instead of my situation where its from many to one.
This is my first post, thanks in advance for the help!
Yes, you can use ...\*\... in a wildcard-based path to represent all directories at a given level of a directory hierarchy.
In your scenario, you could so something like the following (using a local path on a single machine for simplicity) - be sure to run the command elevated (as administrator) so you're permitted to access other users' home directories:
Get-ChildItem C:\Users\*\AppData\Roaming\Microsoft\SystemCertificates\My\Certificates |
Select-Object #{ name='User'; expression={ ($_.FullName -split '\\')[2] } }, FullName |
ForEach-Object {
Copy-Item -WhatIf -Recurse -Force -LiteralPath $_.FullName "\\some\share\$($_.User)"
}
Get-ChildItem C:\Users\*\... implicitly loops over all user home directories and returns the directory at the specified remaining path for each, if present.
The Select-Object call transforms the System.IO.DirectoryInfo instances emitted by Get-ChildItem into custom objects with .FullName (the full directory path) and .User properties (the username implied by the path), by way of a calculated property.
The ForEach-Object call then uses Copy-Item to copy each directory to a destination directory on the target share named for the user; -Recurse copies the directory and all its contents (the directory's whole subtree), and -Force includes hidden items that would be excluded by default.
The -WhatIf common parameter in the Copy-Item command previews the operation. Remove -WhatIf once you're sure the operation will do what you want.

PowerShell script to find registry key

i need some help, i want to create a powershell script that searches the registry for just the key RebootRequired, no value or data search is needed.
with a IF find create a txt file named RebootRequired.txt in folder C:\Candi\
is that even possible?
been trying out some scripting, but i can barley make the script to find the key if it present within the registry.
You could retrieve all keys with Get-ChildItem -Recurse and then filter on key names with Where-Object.
The Registry provider is a little different from the FileSystem provider, in that the Name property of each item is the entire key path (ie. HKEY_LOCAL_MACHINE\Software\Microsoft instead of just Microsoft). You can use PSChildName to refer to the leaf name:
if(#(Get-ChildItem HKLM: -Recurse |Where-Object {$_.PSChildName -eq 'RebootRequired'}))
{
# Something was returned! Create the file
New-Item C:\Candi\RebootRequired.txt -ItemType File
}
You can suppress error messages from inaccessible keys with the -ErrorAction SilentlyContinue parameter argument with Get-ChildItem

Powershell's Copy-Item gives inconsistent results on file system and registry

I am trying to use Powershell's Copy-Item commandlet with the -Recurse and -Force parameters to copy settings from one registry key and overwrite the same settings (if they exist) in another key. If I were using this same command on a file system, then any folders that exist in source and target would be overwritten in the target.
When using this command on the registry, however, if there are keys in the target that exist, the source key is copied as a subkey. Example:
Starting state:
HKCU:Software\OldVendor\Program
HKCU:Software\OldVendor\Program\Setting1
HKCU:Software\OldVendor\Program\Setting2
HKCU:Software\NewVendor\Program
HKCU:Software\NewVendor\Program\Setting1
HKCU:Software\NewVendor\Program\Setting2
Now if I run this command:
Copy-Item -Path "HKCU:Software\OldVendor\Program" -Destination "HKCU:Software\NewVendor" -Recuse -Force
I expect the same structure to be maintained. In fact, the structure under NewVendor looks like this:
HKCU:Software\NewVendor\Program
HKCU:Software\NewVendor\Program\Setting1
HKCU:Software\NewVendor\Program\Setting1\Setting1
HKCU:Software\NewVendor\Program\Setting2
HKCU:Software\NewVendor\Program\Setting2\Setting2
Can anyone tell me how to get Powershell to overwrite existing registry keys, instead of copying to subkeys?
It seems to me you want to copy items and their values, those are properties, not keys. You can list them as follows:
get-itemproperty "HKCU:Software\NewVendor\Program"
or u can use alias and omit quotes:
gp HKCU:Software\NewVendor\Program
Another, better way, which doesn't view powershell specific properties:
Get-Item HKLM:\SOFTWARE\NewVendor\Program | select -ExpandProperty Property
So, if u want to copy all the properties from one key to another:
Get-Item HKLM:\SOFTWARE\Program | select -ExpandProperty Property | % {Copy-ItemProperty -Path HKLM:\SOFTWARE\Program -Destination HKLM:\SOFTWARE\AnotherProgram -Name $_ -Verbose}
You have to use foreach loop, beacuse Copy-ItemProperty cmdlet can copy only one property been specified.