PowerShell - Problems Passing Variables into ForEach Loop - foreach

I am trying to enumerate a list of servers from Active Directory, and then insert the server name into a UNC path as part of a copy command.
When I execute the script, I get the result below. I think that maybe I have to convert my variable, but I am not sure what to convert it to.
VERBOSE: Performing the operation "Copy File" on target "Item: C:\davidtemp\Logo.png Destination: \#{name=NCIDITSTWEB07}\c$\program files...
$webdev = Get-ADOrganizationalUnit -filter {name -like "*dev*"} | where {$_.DistinguishedName -like "*relativity*"}
$ServerList = Get-ADComputer -SearchBase $webdev | where {$_.name -like "*web*"} | select name | sort name
Foreach($server in $ServerList)
{
$scriptBlockwork = { copy C:\davidtemp\Logo.png "\\$server\c$\program files\web\images" -Force -Verbose}
Invoke-Command -ScriptBlock $scriptBlockwork -verbose
}

I reached out to a friend who was able to help. I was not defining the variable properly.
I needed to use -expandProperty to get the results into a format that worked with the pipeline
$ServerList = Get-ADComputer -SearchBase $webdev | where {$_.name -like "web"} | select -expandProperty name
Hopefully this helps someone else who might be having a similar issue.

Related

How do I turn this simple command into something that loops from a list of remote systems?

I received a request to run this on each of my systems which pulls a list of installed applications and outputs it into a text file. I then have to combine all of these things into something more readable which will take a while. I am learning Powershell and want to make this be executed from one system, pull from a list of servers in a text file and run this query from one place against all of the systems:
Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Format-Table –AutoSize > "$Env:userprofile\desktop\Installed Programs for $env:computername.txt"
I've started working on it but am thinking I am missing something to get this to work. I am currently piping this to a string to then output to a csv (I am open to suggestions). This is what I have so far.
# Computer running this script
$WhoAmI = $env:ComputerName
$ServerList = get-content -path "C:\scripts\ServerList.txt"
$Path = "C:\scripts\results"
foreach ($server in $ServerList)
{
$InstalledApps = Invoke-Command -ComputerName $server {Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* }
$Results += $InstalledApps |
Select-Object DisplayName, DisplayVersion, Publisher, InstallDate |
Out-String
}
Write-Host $InstallApps
# $InstallApps | export-csv "$Path\InstalledFiles.csv"
I currently am testing the functionality of the loop by just trying to get it to write to the screen. I only get a blank response.
You weren't getting output because you used the (undefined) variable $InstallApps instead of the variable $results.
With that said, I wouldn't recommend doing string concatenation in a loop. Something like this would be a more elegant approach:
Get-Content -Path 'C:\scripts\ServerList.txt' | ForEach-Object {
$server = $_
Invoke-Command -ComputerName $server -ScriptBlock {
Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*
} | Select-Object #{n='Server';e={$server}}, DisplayName, DisplayVersion,
Publisher, InstallDate
} | Export-Csv 'C:\scripts\results\InstalledPrograms.csv' -NoType
I kind of figured it out. Rested eyes on a fresh day. Some mistakes in what I am writing out, etc. I am open to improvements if anyone has anything to contribute.
EDIT: I got it working mostly with the following but the output is messy. Open to suggestions.
# Computer running this script
$ServerList = get-content -path "C:\scripts\ServerList.txt"
$Path = "C:\scripts\results"
foreach ($server in $ServerList)
{
$Results += "Results for $server"
$InstalledApps = Invoke-Command -ComputerName $server -ScriptBlock {Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* }
$Results += $InstalledApps |
Select DisplayName, DisplayVersion, Publisher, InstallDate |
Out-String
}
# Write-Host $Results
$Results | out-file -filepath "$Path\InstalledPrograms.txt" -width 200

Powershell wildcard on returns items

I'm trying to determine whether the directories under a specified root directory contain files that match a certain pattern, in my case RT*.dcm.
I'm using Powershell 2.0 and I first obtain all sub-directories beneath the specified root directory using
$dirList = Get-ChildItem $homeDir -recurse | where {$_.Attributes -eq 'Directory'} | Select-Object FullName
I then loop through these to see if they contain *.dcm files using (perhaps there's a better way?)
# Find files with a "dcm" extension.
$fileList = Get-ChildItem $dir.fullname | where {$_.extension -eq ".dcm"} | Select-Object FullName
# Look for directories that contain *.dcm files
if ($fileList.Count -gt 0) {
[Console]::WriteLine("Dicom directory: " + $dir.fullname)
$dicomDirList += $dir
}
The above two sections work ok
I then search through the found directories using
foreach($dir in $dicomDirList) {
$rtFileList = Get-ChildItem $dir | where {$_.name -like "RT*.dcm"} | Select-Object FullName
foreach($file in $rtFileList) {
[Console]::WriteLine("RT likey file: " + $file.fullname)
}
}
However this doesn't find the files I know that are there?
If I use
Get-ChildItem C:\myfolder\RT*.dcm
this works, but I can't figure out how to use the returned items from the previous Get-ChildItem call
Could someone please point me in the right direction?
It looks like you may be over-complicating things.
To accomplish what Get-ChildItem C:\myfolder\RT*.dcm does for the entirety of $homeDir (which is what I believe you're trying to do), you can use a single Get-ChildItem command:
Get-ChildItem $homeDir -Recurse | Where-Object{$_.Name -like "RT*.dcm"}
This searches the entirety of $homeDir recursively for all of the .dcm files you're looking for and returns them.

Comparing Registry Keys on Two Servers using Powershell

I would like to compare two Servers registry keys to make sure they both match.
Something simple like this was the initial plan:
$remote1 = (invoke-command -computername hostname `
{Get-ItemProperty "HKLM:SOFTWARE\VENDOR\APP\SUBFOLDER"})
$local1 = (invoke-command `
{Get-ItemProperty "HKLM:SOFTWARE\VENDOR\APP\SUBFOLDER"})
$compare1 = Compare-Object $local1 $remote1
That works great for one single specified key but I have multiple keys with sub folders. I can't provide a list of the ones I want to check (and loop round) as I want to make sure nothing new has been added. So I was drawn down this route to get all the keys under a specified branch in the Registry (to get me a list):
$local1 = Get-ChildItem HKLM:SOFTWARE\MICROSOFT\DirectShow -Recurse `
-ErrorAction SilentlyContinue
So I now have an object that will tell me all the registry SUBFOLDERS on the server and I could then loop round using $local1.PSPath to give me all the paths but I noticed something in the object that was interesting:
$local1 | select -first 1 -prop *
This returns:
Property : {dbl3, dbl4, dbl5, dbl6...}
PSPath : A path
PSParentPath : A Parent Path
PSChildName : 0
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry
PSIsContainer : True
SubKeyCount : 0
View : Default
Handle : Microsoft.Win32.SafeHandles.SafeRegistryHandle
ValueCount : 8
Name : A Name
So the Object member "Property" contains what looks like an array of all the keys or is it a sub Object?
If it was a sub-object does it contain the keys values that I am looking to compare?
I could just snatch out the $local.Name member and loop round comparing using the code above and storing any differences but I just wondered if it would be more efficient to use the data that I already have if it contains the information I need?
I am hoping that someone could confirm that if I did:
$local1 = Get-ChildItem HKLM:SOFTWARE\MICROSOFT\DirectShow -Recurse `
-ErrorAction SilentlyContinue
$remote1 (invoke-command -computername remoteserver1 `
{Get-ChildItem HKLM:SOFTWARE\MICROSOFT\DirectShow -Recurse `
-ErrorAction SilentlyContinue})
When I do the compare am I actually comparing the keys and values match or just that keys exist? :
Compare-Object $local1 $remote1
To cut a long story short, I think I have all the data I need to compare that the Registry key values match (by running this):
$local1
Returns (extract):
Hive: HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\DirectShow
Name Property
---- --------
Debug
DoNotUse
DoNotUseDrivers32
Preferred {00001602-0000-0010-8000-00aa00389b71} : {E1F1A0B8-BEEE-490D-BA7C-066C40B5E2B9}
{e06d8032-db46-11cf-b4d1-00805f6cbbea} : {E1F1A0B8-BEEE-490D-BA7C-066C40B5E2B9}
{00000160-0000-0010-8000-00aa00389b71} : {2eeb4adf-4578-4d10-bca7-bb955f56320a}
{41564D57-0000-0010-8000-00AA00389B71} : {82d353df-90bd-4382-8bc2-3f6192b76e34}
{e06d8026-db46-11cf-b4d1-00805f6cbbea} : {212690FB-83E5-4526-8FD7-74478B7939CD}
Am I correct and does anyone know how to access individual items from the $local1 object? Taking the example above, what is a Hive? Say I wanted the "00001602-0000-0010-8000-00aa00389b71" value how would I get it from the $local1 object.
A point to note that the servers are running Powershell 2 while I am testing on Powershell 4 (I can't test on a Production server). I mention this as running $local1 on the v2 servers I get a different output in that the properties do not seem to be in pairs.
Hive: HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\DirectShow
SKC VC Name Property
--- -- ---- --------
0 0 Debug {}
0 0 DoNotUse {}
0 0 DoNotUseDrivers32 {}
0 10 Preferred {{00000050-0000-0010-8000-00AA00389B71}, {e436eb80-524f-11ce-9f53-0020af0ba77...
Is this case where the v4 objects will do what I want but the v2 won't?
Well fought my way back through the tumble-weed on this one to answer my own question. I had to assume that PowerShell V2 did not contain the key values as I couldn't find a way of extracting them so to be sure I was performing a comparison of the keys and key values I went with this:
foreach ( $KeyPath in $RegPath )
{
$_Path = (invoke-command {Get-ChildItem $KeyPath -Recurse -ErrorAction SilentlyContinue})
ForEach ($key in $_Path)
{
$local = #()
$remote = #()
ForEach ( $Property in $key.Property )
{
if ( $Key.Name -like "*HKEY_LOCAL_MACHINE*" )
{
$KeyPath = [regex]::Replace($key.Name,[regex]"HKEY_LOCAL_MACHINE\\","HKLM:")
} elseif ( $Key.Name -like "*HKEY_CURRENT_USER*" ) {
$KeyPath = [regex]::Replace($key.Name,[regex]"HKEY_CURRENT_USER\\","HKCU:")
} else {
Write-Host "I was unable to set the Registry Hive path, exiting........"
exit 1
}
$lentry = (invoke-command {Get-ItemProperty -Path $KeyPath -Name $Property})
$lfound = New-Object -TypeName PSObject
$lfound | Add-Member -Type NoteProperty -Name Path -Value $KeyPath
$lfound | Add-Member -Type NoteProperty -Name Name -Value $Property
$lfound | Add-Member -Type NoteProperty -Name Data -Value $lentry.$Property
$local += $lfound
$rentry = (invoke-command -computername remoteservername -Script {Get-ItemProperty -Path $args[0] -Name $args[1]} -Args $KeyPath, $Property -ErrorVariable errmsg 2>$null)
if ( $errmsg -like "*does not exist at path*" )
{
$Value = "KEY IS MISSING"
} else {
$Value = $rentry.$Property
}
$rfound = New-Object -TypeName PSObject
$rfound | Add-Member -Type NoteProperty -Name Path -Value $KeyPath
$rfound | Add-Member -Type NoteProperty -Name Name -Value $Property
$rfound | Add-Member -Type NoteProperty -Name Data -Value $Value
$remote += $rfound
}
$compare = Compare-Object -ReferenceObject $local -DifferenceObject $remote -Property Path,Name,Data
$results += $compare
}
}
You then have all the results in an $results object that can be worked on. I used the object in an HTML table similar to this Powershell Hash Table to HTML.
I think it would have been much simpler in PowerShell version four with no need to build $lfound and $rfound.
In powershell v4 you can just export the registry keys at whatever point you want and then use the following to compare them:
$badReg = Get-content C:\Data\badReg.reg
$goodReg = Get-ChildItem C:\Data\goodReg.reg
$results = Compare-Object -ReferenceObject $goodReg -DifferenceObject $badReg

How to select all files of a given filetype EXCEPT ones matching a name pattern?

I'm trying to select all files of a certain type in a given directory EXCEPT ones beginning with certain names. Why didn't this code work?
PS C:\Documents and Settings\wdennis> Get-Item -Path ($AppDir + "reports\*.dbf") | Where-Object {$_.Name -ne "reports*" -or "category*"}
Directory: C:\Program Files\Application\reports
Mode LastWriteTime Length Name
---- ------------- ------ ----
----- 1/4/2007 9:37 AM 4842 category.dbf
----- 9/7/2007 1:53 PM 43903 reports.dbf
I'm pretty new to PS, and very tired to boot, so maybe that's why I'm not understanding why this didn't work. How to do this?
I think that -eq and -ne match the given string and don't support wildcards.
Only -like supports wildcards for pattern matching.
You can however use a regular expression with the -notmatch switch to achieve what you want. Since it's a regular expression now you need to use .* instead of *. And the beginning is marked with ^.
So you end with this
{$_.Name -notmatch "^reports.*|^category.*"}
The whole command
Get-Item -Path ($AppDir + "reports\*.dbf") | Where-Object {$_.Name -notmatch "^reports.*|^category.*"}

Powershell: Count items in a folder with PowerShell

I'm trying to write a very simple PowerShell script to give me the total number of items (both files and folders) in a given folder (c:\MyFolder). Here's what I've done:
Write-Host ( Get-ChildItem c:\MyFolder ).Count;
The problem is, that if I have 1 or 0 items, the command does not work---it returns nothing.
Any ideas?
You should use Measure-Object to count things. In this case it would look like:
Write-Host ( Get-ChildItem c:\MyFolder | Measure-Object ).Count;
or if that's too long
Write-Host ( dir c:\MyFolder | measure).Count;
and in PowerShell 4.0 use the measure alias instead of mo
Write-Host (dir c:\MyFolder | measure).Count;
I finally found this link:
https://blogs.perficient.com/microsoft/2011/06/powershell-count-property-returns-nothing/
Well, it turns out that this is a quirk caused precisely because there
was only one file in the directory. Some searching revealed that in
this case, PowerShell returns a scalar object instead of an array.
This object doesn’t have a count property, so there isn’t anything to
retrieve.
The solution -- force PowerShell to return an array with the # symbol:
Write-Host #( Get-ChildItem c:\MyFolder ).Count;
If you need to speed up the process (for example counting 30k or more files) then I would go with something like this..
$filepath = "c:\MyFolder"
$filetype = "*.txt"
$file_count = [System.IO.Directory]::GetFiles("$filepath", "$filetype").Count
Only Files
Get-ChildItem D:\ -Recurse -File | Measure-Object | %{$_.Count}
Only Folders
Get-ChildItem D:\ -Recurse -Directory | Measure-Object | %{$_.Count}
Both
Get-ChildItem D:\ -Recurse | Measure-Object | %{$_.Count}
You can also use an alias
(ls).Count
Recursively count files in directories in PowerShell 2.0
ls -rec | ? {$_.mode -match 'd'} | select FullName, #{N='Count';E={(ls $_.FullName | measure).Count}}
In powershell you can to use severals commands, for looking for this commands digit: Get-Alias;
So the cammands the can to use are:
write-host (ls MydirectoryName).Count
or
write-host (dir MydirectoryName).Count
or
write-host (Get-ChildrenItem MydirectoryName).Count
To count the number of a specific filetype in a folder.
The example is to count mp3 files on F: drive.
( Get-ChildItme F: -Filter *.mp3 - Recurse | measure ).Count
Tested in 6.2.3, but should work >4.

Resources