Compare CSV to Array - powershell

I have an empty array that's storing all my windows services that start with certain strings such as OPS-AmazonServer,not included in the code I provided is where I parse the service to just say it's application name.
I then have a CSV file with a list of service names labeled under 'Application Name'. It looks like this
ApplicationName,Instance,Priority
AuthBridge,,1
AmazonServer,,1
AmexEC,,1
What I want to do is compare the service stored in the array to the CSV list but I can't seem to figure out how the logic flows.
$services = get-service Centinel* -ComputerName $serverName | select -expand name
$centinelServices = #()
$services = get-service OPS* -ComputerName $serverName | select -expand name
$opsServices = #()
$services = #()
foreach($service in $centinelServices) {
$services += $service
}
foreach($service in $opsServices) {
$services += $service
}
$csvLocation = "\\logserver\Cardinal\OPS\QA\Task\conf\Centinel\app-restart.csv"
$masterList = import-csv $csvLocation
$applications = #()
$masterList | ForEach-Object {$applications += $_.ApplicationName}
forEach($service in $services){
forEach($application in $applications){
if($service -eq $application){
"$service match found"
}
else {
"$service match not found"
}
}

Ok, easiest way to do this is to use Compare-Object, and a little magic with Select.
I'm going to assume that the ApplicationName column in your CSV is a list of strings that match up with the Name property in your Windows Services list. So let's start by importing that CSV, and changing the property name of ApplicationName to just Name, so that it matches the related property on your Windows Service objects.
$masterList = Import-Csv $csvLocation | Select #{l='Name';e={$_.ApplicationName}}
Then we simply use Compare-Object to see what's in both lists:
Compare-Object (Get-Service) -DifferenceObject $masterList -Property Name -IncludeEqual
If you wanted to parse that you can always pipe it to a Where clause, or use combinations of -IncludeEqual and -ExcludeDifferent parameters:
$masterList = Import-Csv $csvLocation | Select #{l='Name';e={$_.ApplicationName}}
$myServices = Get-Service
$foundServices = Compare-Object $myServices -DifferenceObject $masterList -Property Name -IncludeEqual -ExcludeDifferent
$servicesNotInMaster = Compare-Object $myServices -DifferenceObject $masterList -Property Name | Where {$_.SideIndicator -eq '<='}
$servicesNotFoundLocally = Compare-Object $myServices -DifferenceObject $masterList -Property Name | Where {$_.SideIndicator -eq '=>'}
Or using the Switch cmdlet to do it all in one go:
$masterList = Import-Csv $csvLocation | Select #{l='Name';e={$_.ApplicationName}}
$myServices = Get-Service
Switch(Compare-Object $myServices -dif $masterList -prop Name -includeequal -PassThru){
{$_.SideIndicator -eq '<='} {[array]$servicesNotInMaster += $_}
{$_.SideIndicator -eq '=>'} {[array]$servicesNotFoundLocally += $_}
{$_.SideIndicator -eq '=='} {[array]$foundServices += $_}
}
Edit: Ok, updating from your addition to the OP. Looks like you could be well served by simply using a Where clause rather than getting services over and over.
$services = Get-Service -ComputerName $serverName | Where{$_.Name -like 'ops*' -or $_.Name -like 'Centinel*'} | Select -Expand Name
Then you import your CSV, and use Select -Expand again to get the value of the property, rather than looping through it like you were before.
$masterList = Import-Csv $csvLocation | Select -Expand ApplicationName
Now you just have two arrays of strings, so this actually gets even simpler than comparing objects... You can use the -in operator in a Where statement like this:
$services | Where{$_ -in $masterList} | ForEach{"$_ match found"}
That basically filters the $services array to look for any strings that are in the $masterList array. This will only work for exact matches though! So if the service is listed as 'OPS-AmazonServer', but in your CSV file it is listed at just 'AmazonServer' it will not work! I use that example specifically because you have that in your example in your question. You specifically call out the service named 'OPS-AmazonServer' and then in your CSV sample you list just 'AmazonServer'.
If the listings in the CSV are partial strings that you want to match against you could use RegEx to do it. This will probably make less sense if you aren't familiar with RegEx, but this would work:
$services = Get-Service -ComputerName $serverName | Where{$_.Name -like 'ops*' -or $_.Name -like 'Centinel*'} | Select -Expand Name
$masterList = (Import-Csv $csvLocation | ForEach{[regex]::escape($_.ApplicationName)}) -join '|'
$services | Where{ $_ -match $masterList } | ForEach{"$_ match found"}

Related

PowerShell compare CSV to AD Subnets and update / add if required

I've got a CSV file with headers subnet, site, description. I want to import this into PoSH and then compare what is in AD Sites and Services. Now, this might be "null" or it might already contain subnet values. In either case, I need the subnets in AD compared to the CSV and updated/added as required.
I thought about If/Else, but that doesn't seem to handle the scenario where no subnets exist.
So I moved to try/catch and also Compare-Object.
Anyway, an example of what I've tried:
$csvSubnets = Import-Csv -Path C:\subnets.csv
$adSubnets = Get-ADReplicationSubnet -Filter *
Compare-Object -ReferenceObject #($csvSubnets | Select-Object) -DifferenceObject #($adSubnets | Select-Object) -Property Name | Where-Object {
$_.SideIndicator -eq '<='
} | ForEach-Object {
$csvSubnetName = $_.subnet
$csvSiteName = $_.site
$csvDescription = $_.description
$adSubnetName = $adSubnets.Where({$_.Name -eq $csvSubnetName})
New-ADReplicationSubnet -Site $_.site -Name $_.subnet -Description $_.description
}
This returns the following error:
New-ADReplicationSubnet : Cannot validate argument on parameter 'Site'. The argument is null
Would anyone have a suggestion as to how to fix this, or perhaps a more efficient way of achieving what I need? Doesn't need to be computationally efficient as there's probably never more than 10 sites (subnets)
The main thing I'd point out is you aren't passing the original objects coming out of the comparison to the ForEach-Object loop. Either reference the property $_.InputObject or preferably use the -PassThru parameter on the Compare-Object command:
Compare-Object -ReferenceObject #($csvSubnets | Select-Object) -DifferenceObject #($adSubnets | Select-Object) -Property Name -PassThru |
Where-Object { $_.SideIndicator -eq '<='} |
ForEach-Object {
$csvSubnetName = $_.subnet
$csvSiteName = $_.site
$csvDescription = $_.description
$adSubnetName = $adSubnets.Where({$_.Name -eq $csvSubnetName})
New-ADReplicationSubnet -Site $_.site -Name $_.subnet -Description $_.description
}
An aside:
The Compare-Object command is rather hard to read, given intermingled pipelines. It may be better to establish those as variables separately. However, you aren't specifying any properties in those Select-Object commands. I presume you are trying to level-set the typing to [PSCustomObject]'s in which case you should really only need to do that with the rich objects returned from Get-ADReplicationSubnet Import-Csv will always return [PSCustomObject]. So you could revise a little like:
$csvSubnets = Import-Csv -Path 'C:\subnets.csv'
$adSubnets = Get-ADReplicationSubnet -Filter * | Select-Object *
Compare-Object -ReferenceObject $csvSubnets -DifferenceObject $adSubnets -Property Name -PassThru |
Where-Object { $_.SideIndicator -eq '<='} |
ForEach-Object {
$csvSubnetName = $_.subnet
$csvSiteName = $_.site
$csvDescription = $_.description
$adSubnetName = $adSubnets.Where({$_.Name -eq $csvSubnetName})
New-ADReplicationSubnet -Site $_.site -Name $_.subnet -Description $_.description
}
I can't test your code but after solving the first problem you may have another with this line:
$adSubnetName = $adSubnets.Where({$_.Name -eq $csvSubnetName})
You aren't referencing the .Name property after you've isolated the AD Subnet you're interested in. I might be missing something, but if you do have that problem a quick fix might be something like:
$adSubnetName = ($adSubnets.Where({$_.Name -eq $csvSubnetName})).Name
Also, I like the .Where() method, but I'm a little iffy on it's return types, and on the readability of unnecessarily mixing and matching. You could replace with a regular Where{} clause:
$adSubnetName = ( $adSubnets | Where-Object{ $_.Name -eq $csvSubnetName } ).Name
Update:
It is true that if the difference object is null the Compare-Object command will fail. However, that approach stemmed from your original code. There are actually many ways to extract the difference between 2 lists. Here's a more concise example:
$csvSubnets = Import-Csv -Path 'C:\subnets.csv'
$adSubnets = (Get-ADReplicationSubnet -Filter *).Name
$csvSubnets |
Where-Object{ $_.Name -notin $adSubnets } |
ForEach-Object{
$SubnetParams = #{
Name = $_.subnet
Site = $_.site
Description = $_.description
}
New-ADReplicationSubnet #SubnetParams
}
In this case we use the -notin operator against a list of subnet names. $adSubnets isn't being used for anything else. We don't need it to be objects custom or otherwise. Now for each record in the CSV file just see if the name isn't on the list, and if not proceed to create the subnet.
Speed is not a likely concern in this case however you can actually make the above example a little more efficient using If logic in the loop and eliminating the Where{}.
$csvSubnets = Import-Csv -Path 'C:\subnets.csv'
$adSubnets = (Get-ADReplicationSubnet -Filter *).Name
$csvSubnets |
ForEach-Object{
If($_.Name -notin $adSubnets ) {
$SubnetParams = #{
Name = $_.subnet
Site = $_.site
Description = $_.description
}
New-ADReplicationSubnet #SubnetParams
}
}
In either example the key point is that -notin will work fine even if the right hand side of the comparison is null. It's simply return false and therefore the subnet will get created.
Firstly, thank you to the other responses here which lead me down a slightly different path to a solution.
$VerbosePreference = 'Continue'
$csvSubnets = Import-Csv -Delimiter "," -Path C:\subnets.csv
foreach ($subnet in $csvSubnets) {
$ADSubnetName = $subnet.subnet
if ($S = Get-ADReplicationSubnet -Filter {Name -eq $ADSubnetName}) {
Write-Verbose "Subnet $($ADSubnetName) already present"
Continue # to the next subnet
}
$SubnetParams = #{
Name = $subnet.subnet
Site = $subnet.site
Description = $subnet.description
}
Write-Verbose "Created subnet $($ADSubnetName)"
New-ADReplicationSubnet #SubnetParams
}

Comparing two CSV's for similar strings

I am currently trying to compare two CSV's. doc1 has 5000 strings in it, and doc2 has 100 strings in it. Every string in doc2 ends in "admin". I want to compare doc1 & doc2 and find all the strings that match up to the point it reaches "admin".
so as an example:
a string in doc1
a string in doc1 admin
it will output both of these to a new CSV
results = foreach ($OU in $OUs) {
Get-ADGroup -SearchBase $OU -Filter * -Properties CanonicalName,Created,Description,ManagedBy,Member,MemberOf,Members,ObjectGUID,whenChanged |
? {($_.ManagedBy -like $null) -and ($_.Description -notlike "*owner*") -and ($_.CanonicalName -notlike "*admin")}
}
$results | select CanonicalName,Description,ManagedBy,Member,MemberOf,Members,ObjectGUID,Created,whenChanged |
Export-Csv .\doc1.csv -NoTypeInformation
$results0 = foreach ($OU in $OUs) {
Get-ADGroup -SearchBase $OU -Filter * -Properties CanonicalName,Created,Description,ManagedBy,Member,MemberOf,Members,ObjectGUID,whenChanged |
? {($_.ManagedBy -like $null) -and ($_.Description -notlike "*owner*") -and ($_.CanonicalName -like "*admin")}
}
$results0 | select CanonicalName,Description,ManagedBy,Member,MemberOf,Members,ObjectGUID,Created,whenChanged |
Export-Csv .\doc2.csv -NoTypeInformation
$csvPath1 = ".\doc1.csv"
$csvPath2 = '.\doc2.csv'
$propertyToCompare = 'CanonicalName'
$csv1 = Import-Csv -Path $csvPath1
$csv2 = Import-Csv -Path $csvPath2
$duplicates = Compare-Object $csv1 $csv2 -Property $propertyToCompare -ExcludeDifferent -PassThru | Select-Object -ExpandProperty $propertyToCompare
$csv1 | Where-Object { $_.$propertyToCompare -in $duplicates } | Export-Csv -Path .\AdminsAndNotAdminsInOneFile.csv -NoTypeInformation
With Compare-Object I don't know how to make it ignore the last few characters in the string in doc2. Is there some way for me to modify the string?
You can take advantage of the fact that Compare-Object supports calculated properties as comparison properties (here, only a script block ({...}) is passed, which is the same as passing
#{ Expression = { ... } }):
Compare-Object $csv1 $csv2 -ExcludeDifferent -IncludeEqual -PassThru -Property {
$_.$propertyToCompare -replace ' admin$'
} | Select-Object -ExpandProperty $propertyToCompare
Note that -PassThru ensures that the input objects are passed through, which in the case of objects that compare as equal means that the LHS ($csv1) object is passed through.
Expression $_.$propertyToCompare -replace ' admin$', which is evaluated for each input object, uses regex admin$ to replace ' admin' at the end ($) of the value of property $propertyToCompare; if that string isn't present, the value is used as-is.
In effect, the objects are compared as if the trailing ' admin' weren't present.

Powershell Export-CSV

I'm just having problems trying to export the following to a CSV, I've tried putting the Export-CSV within the Foreach-Object loop to no avail.
I want to put the server name $server in the first column and the description $description in the second.
In an earlier version, I was able to make a text file using Out-File -append .\outa.txt -InputObject $_.Name,$description but the formatting did not work well.
$server = Get-ADComputer -Filter {OperatingSystem -Like "*Server*" -and Name -Notlike "*DOM*"}
$server | ForEach-Object {
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$_.Name)
$RegKey = $Reg.OpenSubKey("SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters")
$description = $RegKey.GetValue("srvcomment")
} | Export-Csv .\out1.csv
A ForEach as oppose to ForEach-Object:
$server = Get-ADComputer -Filter {OperatingSystem -Like "*Server*" -and Name -Notlike "*DOM*"}
foreach($s in $server){
# replaced $_ with $s
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$s.Name)
$RegKey= $Reg.OpenSubKey("SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters")
$description = $RegKey.GetValue("srvcomment")
# casting as array is required to use +=
# disclaimer: the array is destroyed and rebuilt on each run.
[array]$myData += New-Object psobject -Property #{
Server = $s.Name
Description = $description
}
}
# If you want a CSV without the top info line, use the notypeinfo switch
# Select-Object gives you the column order you want.
$myData | Select-Object Server,Description | Export-Csv .\out1.csv -NoTypeInformation
Edit - comment answer
It is possible to do it without creating an array and from inside the loop, but an object is required by Export-Csv AFAIK.
ForEach-Object:
$server = Get-ADComputer -Filter {OperatingSystem -Like "*Server*" -and Name -Notlike "*DOM*"}
$server | ForEach-Object {
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$_.Name)
$RegKey = $Reg.OpenSubKey("SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters")
New-object psobject -Property #{Server=$_.Name;Description=$RegKey.GetValue("srvcomment")} | Export-Csv .\out1.csv -NoTypeInformation
}
ForEach:
$server = Get-ADComputer -Filter {OperatingSystem -Like "*Server*" -and Name -Notlike "*DOM*"}
foreach($s in $server){
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$s.Name)
$RegKey= $Reg.OpenSubKey("SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters")
New-object psobject -Property #{Server=$s.Name;Description=$RegKey.GetValue("srvcomment")} | Export-Csv .\out1.csv -NoTypeInformation
}
If you have problems with the values, brackets/subexpressions can help:
New-object psobject -Property #{Server=($_.Name);Description=($RegKey.GetValue("srvcomment"))} | Export-Csv .\out1.csv -NoTypeInformation
New-object psobject -Property #{Server=$($_.Name);Description=$($RegKey.GetValue("srvcomment"))} | Export-Csv .\out1.csv -NoTypeInformation
Your ForEach-Object only contains statements (variable assignments). It's not returning anything. You need to return something, otherwise you're passing an empty pipeline to Export-CSV.
I'm guessing that, instead of setting variables called $Reg, $RegKey, and $description (and then never using their values), what you actually want to do is create columns in the CSV called Reg, RegKey, and description. In that case, you want to yield a [PSCustomObject] (with those properties added to it) each time through the loop.
$server = Get-ADComputer -Filter {OperatingSystem -Like "*Server*" -and Name -Notlike "*DOM*"}
$server | ForEach-Object {
[PSCustomObject] #{
Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$_.Name)
RegKey = $Reg.OpenSubKey("SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters")
description = $RegKey.GetValue("srvcomment")
}
} | Export-Csv .\out1.csv

PowerShell Where-Object $_.name -like -in $list

I am new to PowerShell and ran into a bit of a roadblock. I am trying to pull program name and version information from multiple servers.
I have a list of the program names in a $list variable, but the program names also contain the version numbers in them. I am just storing the names of the programs in the list variable without the version numbers.
I am trying to figure out a way to use both the -like and -in parameters with the Where-Object cmdlet in order to match the full program entry name (e.g. AdToUserCacheSync 1.10.1.10) with my entry in the $list variable (e.g. AdToUserCacheSync).
How can I do this?
$list = Get-Content "\\server\c$\temp\list.txt"
$storeTestServers = Get-Content "\\server\c$\temp\testStores.txt"
foreach ($server in $storeTestServers) {
Get-WmiObject -Class Win32_Product -ComputerName $server |
Select-Object -Property PSComputerName, Name, Version |
Where-Object {$_.pscomputername -like "940*" -and $_.name -like -in "*$list*"}
}
The Where-Object FilterScript block is just a scriptblock that returns $true, $false or nothing - you can do all kinds of crazy things inside it, including looping over an array to see if there is a wildcard match in one of the entries:
Where-Object {
$ProductName = $_.Name
$_.pscomputername -like "940*" -and (
$list | ForEach-Object {
if($ProductName -like "*$_*"){ return $true }
}
)
}
I found the Adobe version by PowerShell:
Get-WmiObject -Class Win32_Product -ComputerName 127.0.0.1 |
Select-Object -Property PSComputerName, Name, Version |
Where-Object {$_.Name -like "Adobe*"} | Out-File Adobe_Log.log

List all Server Features/Roles in Powershell

So I have the following code to output all features and roles installed:
Import-Module ServerManager
$Arr = Get-WindowsFeature | Where-Object {$_.Installed -match “True”} | Select-Object -Property Name
$loopCount = $Arr.Count
For($i=0; $i -le $loopCount; $i++) {
Write-Host $Arr[$i]
}
However, the output is:
#{Name=Backup-Features}
#{Name=Backup}
#{Name=Backup-Tools}
How can I get rid of the # and {}'s ?
Use Select -ExpandProperty Name instead of Select -Property Name
Alternatively and also, I recommend using Foreach-Object instead of a C-style for loop.
Import-Module ServerManager
Get-WindowsFeature |
Where-Object {$_.Installed -match “True”} |
Select-Object -ExpandProperty Name |
Write-Host
Or
Import-Module ServerManager
Get-WindowsFeature |
Where-Object {$_.Installed -match “True”} |
ForEach-Object {
$_.Name | Write-Host
}
How about a nice one liner?
Get-WindowsFeature | ? {$_.Installed -match “True”} | Select -exp Name
If you can accept a totally static solution, this should work:
Write-Host $Arr[$i].Substring(2, $Arr[$i].Length-3)
If you're looking for a solution that looks specifically for those symbols and removes them, it would be a little different. Based on your question though, this should be just fine.