Avoiding or replacing multiple Where-Object statements - powershell

I have a script containing a function Create-RebootData containing child functions such as Full and Full has a child function named Generate-RebootData where the output variable $Global:result is created.
Within Full there are multiple Where-Object two statements to filter the $Global:result into by date and time. Example below.
Is there an easier method to accomplish this instead of the multiple Where-Object statements?
The desired result are
Set-StrictMode -Version 1.0
Function Create-RebootData{
[CmdletBinding(SupportsShouldProcess=$true,DefaultParameterSetName="ViewOnly")]
Param(
[Parameter(ParameterSetName="ViewOnly")]
[Switch]$ViewOnly,
[Parameter(ParameterSetName="Full")]
[Switch]$Full,
)
Switch ($PSCmdlet.ParameterSetName){
"ViewOnly"
{
ViewOnly
}
"Full"
{
Full
}
}#end switch
Function Full{
Generate-RebootData
$Global:result | Where-Object {$_ -like '*fri?2:00*' -and $_.MaintenanceWindow `
-notmatch 'all.da.servers' -and $_.Server -match "^ITD"} | % {"{0}" -f $_.Server} | `
Out-File D:\Scripts\Full-Servers.txt -Append
$Global:result | Where-Object {$_ -like '*fri?2:00*' -and $_.MaintenanceWindow `
-notmatch 'all.da.servers' -and $_.Server -match "^ITD"} | `
% {"{0}" -f $_.MaintenanceWindow -replace `
"^NA.+", "$((get-date).AddDays(1).ToString('MM-dd-yy')) 01:50"} | `
Out-File D:\Scripts\Full-Times.txt -Append
}
Function Generate-RebootData{
IF(Get-Command Get-SCOMAlert -ErrorAction SilentlyContinue){}ELSE{Import-Module OperationsManager}
"Get Pend reboot servers from prod"
New-SCOMManagementGroupConnection -ComputerName Server01
$AlertData = Get-SCOMAlert -Criteria "MyString" | Select NetbiosComputerName
New-SCOMManagementGroupConnection -ComputerName Server02
$AlertData += Get-SCOMAlert -Criteria "MyString" | Select NetbiosComputerName
"Remove duplicates"
$AlertDataNoDupe = $AlertData | Sort NetbiosComputerName -Unique
"Create hash table"
$table = #{}
"Populate hash table"
$MaintenanceWindow = Import-Csv D:\Scripts\MaintenanceWindow2.csv
$MaintenanceWindow | ForEach-Object {$table[$_.Computername] = $_.'Collection Name'}
"Create final object"
$Global:result = #{}
"Begin Loop"
$Global:result = $AlertDataNoDupe | ForEach-Object { [PSCustomObject] #{
Server=$_.NetbiosComputerName
MaintenanceWindow= if($table.ContainsKey($_.NetbiosComputerName)){
$table[$_.NetbiosComputerName]
}Else { "Not Found!"}
PingCheck=IF(Test-Connection -Count 1 $_.NetbiosComputerName -Quiet -ErrorAction SilentlyContinue){"Alive"}
ELSE{"Dead"}
LastReboot=Try{$operatingSystem = Get-WmiObject Win32_OperatingSystem -ComputerName $_.NetbiosComputerName -ErrorAction Stop
[Management.ManagementDateTimeConverter]::ToDateTime($operatingSystem.LastBootUpTime)}
Catch{"Access Denied!"}
} }
}

You can do it like this:
$Global:result | Where-Object {
$_ -like '*fri?2:00*' -and
$_.MaintenanceWindow -notmatch 'all.da.servers' -and
$_.Server -match '^ITD'
} | ForEach-Object {
'{0}' -f $_.Server | Out-File D:\Scripts\Full-Servers.txt -Append
'{0}' -f $_.MaintenanceWindow -replace '^NA.+',
'{0} 01:50' -f (Get-Date).AddDays(1).ToString('MM-dd-yy') | Out-File D:\Scripts\Full-Times.txt -Append
}
But I agree with Mathias' comment, this function probably should be refactored.

Related

Selection of only one user from list

I have this script that I need to use to retrieve the data of a particular user "ADTuser" from a list of servers the script works well, but the output file with my user add also other users' detail that is not needed for my final output how can I filter it to only the user that I need.
get-content C:\servers.txt | foreach-object {
$Comp = $_
if (test-connection -computername $Comp -count 1 -quiet) {
([ADSI]"WinNT://$comp").Children | ?{$_.SchemaClassName -eq 'user' } | %{
$groups = $_.Groups() | %{$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
$_ | Select #{n='Computername';e={$comp}},
#{n='UserName';e={$_.Name}},
#{n='Memberof';e={$groups -join ';'}},
#{n='status'; e={if($groups -like "*Administrators*"){$true} else{$false}}}
}
} Else {Write-Warning "Server '$Comp' is Unreachable hence Could not fetch data"}
} | Out-File -FilePath C:\users.txt
This should be an easier way of doing what you're looking for, Get-CimInstance and Get-CimAssociatedInstance have been around since PowerShell 3:
Get-Content C:\servers.txt | ForEach-Object {
$computer = $_
try {
$query = Get-CimInstance Win32_UserAccount -Filter "Name='ADTuser'" -ComputerName $_ -ErrorAction Stop
foreach($object in $query) {
$membership = Get-CimAssociatedInstance -InputObject $object -ResultClassName Win32_Group -ComputerName $_
[pscustomobject]#{
Computername = $_
UserName = $object.Name
Memberof = $membership.Name -join ';'
Status = $membership.Name -contains 'Administrators'
}
}
}
catch {
Write-Warning "Server '$computer' is Unreachable hence Could not fetch data"
}
} | Export-Csv C:\users.csv -NoTypeInformation
If that doesn't work for you, your code would require a simple modification on your first filtering statement:
Where-Object { $_.SchemaClassName -eq 'user' -and $_.Name.Value -eq 'ADTuser' }
It's important to note that Test-Connection -ComputerName $_ -Count 1 -Quiet is not a relevant test for this script, this command is testing for ICMP response and adsi over WinNT requires RPC connectivity as well SMB.
Putting it all together with minor improvements the script would look like this:
Get-Content C:\servers.txt | ForEach-Object {
if (-not (Test-Connection -ComputerName $_ -Count 1 -Quiet)) {
Write-Warning "Server '$_' is Unreachable hence Could not fetch data"
return
}
$computer = $_
([adsi]"WinNT://$_").Children.ForEach{
if($_.SchemaClassName -ne 'user' -and $_.Name.Value -ne 'ADTuser') {
return
}
$groups = $_.Groups().ForEach([adsi]).Name
[pscustomobject]#{
Computername = $computer
UserName = $_.Name.Value
Memberof = $groups -join ';'
Status = $groups -contains 'Administrators'
}
}
} | Export-Csv C:\users.csv -NoTypeInformation

Trouble with retrieving certificate information in Powershell?

I'm trying to build a dashboard to retrieve certificate information on all our servers, but I'm struggling with the powershell object handling. I believe it's the way objects are getting passed inside and outside a loop. I have 3 iterations of my code.
In the first, all certificates are retrieved, but the FriendlyName is blanked out on every object:
$serverCert = $null
$servers=get-adcomputer -filter { ( OperatingSystem -like '*server*') -AND ( Name -notlike '*-DT0094' ) } | sort Name
foreach ( $server in $servers ) {
$ServerName=$server.Name
$ServerName="$ServerName.DOMAINSUFFIX"
$serverCert += Invoke-Command -ComputerName $ServerName -Scriptblock {
return $(Get-ChildItem Cert:\LocalMachine\My)
}
}
$serverCert | Select-Object PSComputerName, Thumbprint, FriendlyName, NotAfter, #{N="Template";E={($_.Extensions | ?{$_.oid.Friendlyname -match "Certificate Template Information"}).Format(0) -replace "(.+)?=(.+)\((.+)?", '$2'}}, #{N="IssuedBy";E={($_.IssuerName.Name -split ',*..=')[1]}}, #{N="Subject";E={($_.Subject -split ',*..=')[1]}} | Sort Thumbprint | Format-Table -Wrap
In this iteration, the Extensions come through like this:
PS C:\WINDOWS> $serverCert[0] | Select-Object -Property Extensions
Extensions
----------
{System.Security.Cryptography.Oid, System.Security.Cryptography.Oid}
In the second, I solved this by explicitly passing the FriendlyName through as a new property called "Description"...unfortunately, now the Template doesn't display:
$serverCert = $null
$servers=get-adcomputer -filter { ( OperatingSystem -like '*server*') -AND ( Name -notlike '*-DT0094' ) } | sort Name
foreach ( $server in $servers ) {
$ServerName=$server.Name
$ServerName="$ServerName.DOMAINSUFFIX"
$serverCert += Invoke-Command -ComputerName $ServerName -Scriptblock {
return $(Get-ChildItem Cert:\LocalMachine\My | Select-Object *, #{N="Description";E={$_.FriendlyName}})
}
}
$serverCert | Select-Object PSComputerName, Thumbprint, Description, NotAfter, #{N="Template";E={($_.Extensions | ?{$_.oid.Friendlyname -match "Certificate Template Information"}).Format(0) -replace "(.+)?=(.+)\((.+)?", '$2'}}, #{N="IssuedBy";E={($_.IssuerName.Name -split ',*..=')[1]}}, #{N="Subject";E={($_.Subject -split ',*..=')[1]}} | Sort Thumbprint | Format-Table -Wrap
In this iteration, the Extensions come through like this, and I can't get template name to display:
PS C:\WINDOWS> $serverCert[0] | Select-Object -Property Extensions
Extensions
----------
{System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension, System.Security.Cryptography.X509Certificates.X509KeyUsageExtension}
Now the third. This time I try to pass the template information forward as a property like the "Description." Problem is, the template information doesn't come through clearly; rather than the friendly name, other info is displayed.
$serverCert = $null
$servers=get-adcomputer -filter { ( OperatingSystem -like '*server*') -AND ( Name -notlike '*-DT0094' ) } | sort Name
foreach ( $server in $servers ) {
$ServerName=$server.Name
$ServerName="$ServerName.DOMAINSUFFIX"
$serverCert += Invoke-Command -ComputerName $ServerName -Scriptblock {
return $(Get-ChildItem Cert:\LocalMachine\My | Select-Object *, #{N="Description";E={$_.FriendlyName}}, #{N="Template";E={($_.Extensions | ?{$_.oid.Friendlyname -match "Certificate Template Information"}).Format(0) -replace "(.+)?=(.+)\((.+)?", '$2'}})
}
}
$serverCert | Select-Object PSComputerName, Thumbprint, Description, NotAfter, Template, #{N="IssuedBy";E={($_.IssuerName.Name -split ',*..=')[1]}}, #{N="Subject";E={($_.Subject -split ',*..=')[1]}} | Sort Thumbprint | Format-Table -Wrap
Template information for some certificates (I can't correlate on template, OS version...anything) looks like this:
Template=1.3.6.1.4.1.311.21.8.16245382.12313948.10571683.3565079.1665071.100.15924968.15384388, Major Version Number=100, Minor Version Number=4
I'm stumped. I am still getting comfortable in powershell, but I don't understand object manipulation well enough to know how to fix this. Any help is appreciated!
I think FriendlyName not getting returned in your first iteration has to do with the way PowerShell is deserializing data when the -ComputerName parameter is used.
In your first iteration, try changing this line:
return $(Get-ChildItem Cert:\LocalMachine\My)
to:
return $(Get-ChildItem Cert:\LocalMachine\My | Select-Object *)
To illustrate the issue, run these three commands, which you would expect to all include the same object properties. Note that FriendlyName is only included in the output of the first two commands:
Invoke-Command -ScriptBlock { gci Cert:\LocalMachine\My } | Select-Object FriendlyName
Invoke-Command -ScriptBlock { gci Cert:\LocalMachine\My | Select-Object * } -ComputerName . | Select-Object FriendlyName
Invoke-Command -ScriptBlock { gci Cert:\LocalMachine\My } -ComputerName . | Select-Object FriendlyName
Edit: This is how I might do it:
Function Get-Cert-Info($ComputerName) {
Invoke-Command -Computer $ComputerName -ScriptBlock {
$certs = Get-ChildItem cert:\localmachine\my
foreach($cert in $certs) {
[pscustomobject]#{
ComputerName = $env:COMPUTERNAME
Thumbprint = $cert.Thumbprint
Description = $cert.FriendlyName
TemplateName = $(
$Template = $cert.Extensions | Where-Object { $_.oid.FriendlyName -match "Certificate Template Information" }
if($Template) {
($Template.Format(0) -split "\(")[0] -replace "Template=", ""
}
)
SAN=$(
try {
$cert.Extensions | Where-Object {$_.Oid.FriendlyName -eq "subject alternative name"} | ForEach {
$SANString = "{0}" -f $_.Format(0)
$SANS = $SANString -split ','
foreach($SAN in $SANS) {
($SAN -split "=")[1]
}
}
} catch {
"n/a"
}
)
IssuedBy=$(($cert.IssuerName.Name -split ',')[0] -replace 'CN=', '')
Subject=$(($cert.Subject -split ',')[0] -replace 'CN=', '')
NotAfter=$cert.NotAfter
}
}
}
}
$servers=get-adcomputer -filter { ( OperatingSystem -like '*server*') -AND ( Name -notlike '*-DT0094' ) } | sort Name
foreach ( $server in $servers ) {
Get-Cert-Info -ComputerName $server.Name
}
I found a way to get the template name though, but lookup would fail, so I did something akin to a replace when I got the ID:
# Get all certificates from all servers
clear-host
$serverCert = $null
ipconfig /flushdns
$servers=get-adcomputer -filter { OperatingSystem -like '*server*' } | sort Name
foreach ( $server in $servers ) {
$ServerName=$server.Name
$ServerName="$ServerName.DOMAINSUFFIX"
$serverCert += Invoke-Command -ComputerName $ServerName -Scriptblock {
return $(Get-ChildItem Cert:\LocalMachine\My | Select-Object *, #{N="Description";E={$_.FriendlyName}}, #{N="TemplateName";E={($_.Extensions | ?{$_.oid.Friendlyname -match "Certificate Template Information"}).Format(0) -replace "(.+)?=(.+)\((.+)?", '$2' -replace 'Template=', '' -replace '1.3.6.1.4.1.311.21.8.16245382.12313948.10571683.3565079.1665071.100.15924968.15384388.*', 'SCCM Client Certificate' -replace '1.3.6.1.4.1.311.21.8.16245382.12313948.10571683.3565079.1665071.100.9941395.14900143.*','IIS Web Servers' -replace '1.3.6.1.4.1.311.21.8.16245382.12313948.10571683.3565079.1665071.100.1979823.4984146.*','WSUS Web Server Certificate'}})
}
}
$serverCert | Select-Object PSComputerName, Thumbprint, Description, NotAfter, TemplateName, #{N="IssuedBy";E={($_.IssuerName.Name -split ',*..=')[1]}}, #{N="Subject";E={($_.Subject -split ',*..=')[1]}} | Sort NotAfter, PSComputerName | Format-Table -Wrap

PowerShell - Add string between outputs

I'm trying to separate by a string (;), results from this :
$MasterKeys = ($MasterKeys | Where {$_.Name -ne $Null -AND $_.SystemComponent -ne "1" -AND $_.ParentKeyName -eq $Null} | select-String Name,Version,ComputerName | sort Name| ft -hide )
Here is the output of this command:
HP ePrint SW 5.1.20088 LT00438
I would like this instead:
HP ePrint SW; 5.1.20088; LT00438
Well, this is the full code :
I don't know where to put the Export-Csv -Path "file.txt" -Delimiter ";" -NoTypeInformation function :(
Function Get-InstalledSoftware
{
Param
(
[Alias('Computer','ComputerName','HostName')]
[Parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$true,Position=1)]
[string[]]$Name = $env:COMPUTERNAME
)
Begin
{
$LMkeys = "Software\Microsoft\Windows\CurrentVersion\Uninstall","SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
$LMtype = [Microsoft.Win32.RegistryHive]::LocalMachine
$CUkeys = "Software\Microsoft\Windows\CurrentVersion\Uninstall"
$CUtype = [Microsoft.Win32.RegistryHive]::CurrentUser
}
Process
{
ForEach($Computer in $Name)
{
$MasterKeys = #()
If(!(Test-Connection -ComputerName $Computer -count 1 -quiet))
{
Write-Error -Message "Unable to contact $Computer. Please verify its network connectivity and try again." -Category ObjectNotFound -TargetObject $Computer
Break
}
$CURegKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($CUtype,$computer)
$LMRegKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($LMtype,$computer)
ForEach($Key in $LMkeys)
{
$RegKey = $LMRegKey.OpenSubkey($key)
If($RegKey -ne $null)
{
ForEach($subName in $RegKey.getsubkeynames())
{
foreach($sub in $RegKey.opensubkey($subName))
{
$MasterKeys += (New-Object PSObject -Property #{
"ComputerName" = $Computer
"Name" = $sub.getvalue("displayname")
"SystemComponent" = $sub.getvalue("systemcomponent")
"ParentKeyName" = $sub.getvalue("parentkeyname")
"Version" = $sub.getvalue("DisplayVersion")
})
}
}
}
}
ForEach($Key in $CUKeys)
{
$RegKey = $CURegKey.OpenSubkey($Key)
If($RegKey -ne $null)
{
ForEach($subName in $RegKey.getsubkeynames())
{
foreach($sub in $RegKey.opensubkey($subName))
{
$MasterKeys += (New-Object PSObject -Property #{
"ComputerName" = $Computer
"Name" = $sub.getvalue("displayname")
"SystemComponent" = $sub.getvalue("systemcomponent")
"ParentKeyName" = $sub.getvalue("parentkeyname")
"Version" = $sub.getvalue("DisplayVersion")
})
}
}
}
}
$files = "inventaireLT.txt"
$MasterKeys = ($MasterKeys | Where {$_.Name -ne $Null -AND $_.SystemComponent -ne "1" -AND $_.ParentKeyName -eq $Null} | select Name,Version,ComputerName | sort Name| ft -hide )
$MasterKeys >> $files
}
}
End
{
}
}
Clear-Content -path $files
Import-Module ActiveDirectory
Get-ADComputer -LDAPfilter "(name=LT*)" -SearchBase "OU=S****Y,DC=T***R,DC=com" | Get-InstalledSoftware
(gc inventaireLT.txt) | ? {$_.trim() -ne "" } | set-content inventaireLT.txt
You can export your data to a semicolon separated file by using Export-Csv and specifying the -Delimiter ";"
$MasterKeys | Where {$_.Name -ne $Null -AND $_.SystemComponent -ne "1" -AND $_.ParentKeyName -eq $Null} | select Name,Version,ComputerName | Export-Csv -Path "C:\folder\file.txt" -Delimiter ";" -NoTypeInformation
If you don't want the header line:
$MasterKeys | Where {$_.Name -ne $Null -AND $_.SystemComponent -ne "1" -AND $_.ParentKeyName -eq $Null} |
select Name,Version,ComputerName |
ConvertTo-Csv -Delimiter ";" -NoTypeInformation |
Select-Object -Skip 1 |
Set-Content -Path "C:\folder\file.txt"
EDIT:
In your code you would replace:
$MasterKeys = ($MasterKeys | Where {$_.Name -ne $Null -AND $_.SystemComponent -ne "1" -AND $_.ParentKeyName -eq $Null} | select Name,Version,ComputerName | sort Name| ft -hide )
$MasterKeys >> $files
with
$MasterKeys | Where {$_.Name -ne $Null -AND $_.SystemComponent -ne "1" -AND $_.ParentKeyName -eq $Null} | select Name,Version,ComputerName | ConvertTo-Csv -Delimiter ";" -NoTypeInformation | Select-Object -Skip 1 | Set-Content -Path $files

Cannot bind argument to parameter "FilePath" because it is null

Every time I run the script below I get
Cannot bind argument to parameter 'FilePath' because it is null.
It was working last night. No changes have been made and this morning it just fails. the funny thing is if i save the script and then run it, it works. However if I clear console then run it again it fails.
Am I missing something obvious?
New-Item -ItemType Directory -Force -Path C:\NonStandard_Services
set-location C:\NonStandard_Services
$Computers= Get-Content C:\computers.txt
$Report= $file
$file= $Computer
ForEach ($Computer in $Computers)
{
Get-WmiObject -ComputerName $Computer -class Win32_Service -ErrorAction SilentlyContinue |
Where-Object -FilterScript {$_.StartName -ne "LocalSystem"}|
Where-Object -FilterScript {$_.StartName -ne "NT AUTHORITY\NetworkService"} |
Where-Object -FilterScript {$_.StartName -ne "NT AUTHORITY\LocalService"} |
Select-Object -Property StartName,Name,DisplayName|
ConvertTo-Html -Property StartName,Name,DisplayName -head $HTML -body "<H2>Non- Standard Service Accounts on $Computer</H2>"| Out-File $Report -Append}
#Rename-Item c:\GP_Services\Report.htm $file
Get-ChildItem | Where-Object {$_.extension -ne ".htm"} | Rename-Item -newname { $_.name + '.htm' }
$Report= $file
$file= $Computer
ForEach ($Computer in $Computers)
{
...
}
You assign variables to other variables before they were assigned a value themselves. Change the above to this:
ForEach ($Computer in $Computers) {
$file = $Computer
$Report = $file
...
}
Or directly use $Computer in Out-File:
... | Out-File "$Computer.txt" -Append

Drive variable is not working in Powershell

I have the following script to list big files on list of servers. Unfortunately it is not listing anything. However, if I replace the $_.Name by the string D:\ it works fine.
$servers = Get-Content "servers1.txt" | Select-String -pattern `
"^[^#]"
foreach ($line in $servers) {
$svr = echo $line | %{$_.Line.split(':')[2]}
Get-WmiObject Win32_Volume -ComputerName $svr -ErrorAction SilentlyContinue |
Select-Object __SERVER,Name |
foreach {
Invoke-command {Get-ChildItem -path $_.Name -rec | Where-Object `
-FilterScript {($_.Length -ge 3GB) -and ($_.Name -notlike "*.mdf")}} -computername $svr
}
}
Thanks for any help.
This is scoping issue: in remote command $_.Name does not exist. Try this instead:
Invoke-command {
param ($Path)
Get-ChildItem -path $Path -rec | Where-Object {($_.Length -ge 3GB) -and ($_.Name -notlike "*.mdf")}
} -ComputerName $svr -ArgumentList $_.Name