Powershell Array Enumeration to Log - arrays

I am attempting to enumerate an array of certificates to a log file:
$allCerts = Get-ChildItem Cert:\LocalMachine\My\ | Select-Object name,subject,Thumbprint,Issuer,NotAfter
Then sending to log via logging function:
$allCerts | ForEach-Object {logger -logstr $_}
This shows the following in the log (sensitive info removed) - note the log isn't wrapped, this is one line for each cert:
[2020-0326-1003:11:10] : #{name=; Subject=CN=WinRM HTTPS mycomputer; Thumbprint=12345679679484747463etygvetevtreye; Issuer=CN=WinRM HTTPS computer; NotAfter=03/26/2023 09:38:09}
[2020-0326-1003:11:11] : #{name=; Subject=CN=mycompcert2.abc.de.fg; Thumbprint=fdhshs44he4hhh4the44h4h4; Issuer=CN=Enterprise CA, OU=Cert Svcs, O=A Corporation, L=location, S=state, C=AU; NotAfter=09/20/2050 10:30:00}
The only way I've found so far is to:
$allCerts | ForEach-Object {logger -logstr ($_ | Out-String)}
Which gives:
[2020-0326-0952:11:56] :
name :
Subject : CN=WinRM HTTPS mycomputer
Thumbprint : 12345679679484747463etygvetevtreye
Issuer : CN=WinRM HTTPS computer
NotAfter : 03/26/2023 09:38:09
[2020-0326-0952:11:57] :
name :
Subject : CN=mycompcert2.abc.de.fg
Thumbprint : fdhshs44he4hhh4the44h4h4
CN=Enterprise CA, OU=Cert Svcs, O=A Corporation, L=location, S=state, C=AU
NotAfter : 09/20/2050 10:30:00
Is there a "tidier" way I can enumerate the properties of each member of the array to a single line in the log without having to use | out-string?

your Logger function seems to require a string. so you need to build the string 1st. the default .ToString() method will not give you what you want, tho.
you can do a slightly iffy trick and use ConvertTo-CSV to give you the lines that would be in a CSV file ... and skip the 1st line to get rid of the header line. something like the following ...
[my cert list for the machine has no certs, so i used the current user. also, i have no Name property ... only a FriendlyName property.]
Get-ChildItem Cert:\CurrentUser\My |
Select-Object -Property FriendlyName, Subject, Thumbprint, Issuer, NotAfter |
ConvertTo-Csv -Delimiter ';' -NoTypeInformation |
Select-Object -Skip 1
output ...
"APNS certificate";"CN=95F066ED-5A55-4053-84A0-D49E72C4AF6B";"42DA37600AFD9F0423FDCCF298A89C6CF1F945C8";"CN=Apple iPhone Device CA, OU=Apple iPhone, O=Apple Inc., C=US";"2016-11-03 2:18:59 PM"
"APNS certificate Direct";"CN=8E9D6C89-2BEF-4677-A9AD-84645ABFE44A";"2580F9FD8CB5E49EAC1035940D0D8EC09746A078";"CN=Apple iPhone Device CA, OU=Apple iPhone, O=Apple Inc., C=US";"2019-12-09 3:39:44 AM"
if you want to strip out the quotes, that seems fairly easy. [grin]

Related

Script command won't accept results of array -join but will accept it when typed in manually

I'm adding firewall rules via powershell script. When the script only wants to open a single port (i.e. when $ports=#(80) below) it seems to work fine, but I want to open multiple ports by putting more than one value in the array.
The script uses $ports -join "," to create the string 80,5000 but New-NetFirewallRule rejects the argument. when I type 80,5000 explicity on the command line, then it does not error.
I'm a PS rookie, and have the feeling I'm missing something about the difference between what PS is doing internally and what it presents to me when I evaluate $ports_a. Here's an interactive session reproducing the error and the acceptance of the manually-entered value.
PS C:\Users\Steve\Documents> $firewallRuleName='Test Rule'
PS C:\Users\Steve\Documents> $ports=#(80,5000);
PS C:\Users\Steve\Documents> $ports_a = $ports -join ",";
PS C:\Users\Steve\Documents> New-NetFireWallRule -DisplayName $firewallRuleName -Direction Outbound -Action Allow -Protocol TCP -LocalPort $ports_a
New-NetFireWallRule : The port is invalid. When Protocol is TCP or UDP, individual ports or ranges are allowed. Also, the
following port keywords are allowed on Firewall Rules: RPC, RPCEPMap, Teredo, IPHTTPSIn, IPHTTPSOut, PlayToDiscovery.
At line:1 char:1
+ New-NetFireWallRule -DisplayName $firewallRuleName -Direction Outboun ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (MSFT_NetFirewallRule:root/standardcimv2/MSFT_NetFirewallRule) [New-NetFirew
allRule], CimException
+ FullyQualifiedErrorId : HRESULT 0x80070057,New-NetFirewallRule
PS C:\Users\Steve\Documents> write-output $ports_a
80,5000
PS C:\Users\Steve\Documents> New-NetFireWallRule -DisplayName $firewallRuleName -Direction Outbound -Action Allow -Protocol TCP -LocalPort 80,5000
Name : {09af142c-c82d-4329-aba7-60662f357366}
DisplayName : Test Rule
Description :
DisplayGroup :
Group :
Enabled : True
Profile : Any
Platform : {}
Direction : Outbound
Action : Allow
EdgeTraversalPolicy : Block
LooseSourceMapping : False
LocalOnlyMapping : False
Owner :
PrimaryStatus : OK
Status : The rule was parsed successfully from the store. (65536)
EnforcementStatus : NotApplicable
PolicyStoreSource : PersistentStore
PolicyStoreSourceType : Local
RemoteDynamicKeywordAddresses :
PS C:\Users\Steve\Documents> $ports_a.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
Gah. So the answer was: just pass the array in. I don't need $ports_a and can just pass -LocalPort $ports.
A way to know this is the right way to pass the argument is via Get-Help: it shows that the type of --LocalPort is String [].
PS C:\Users\Steve\Documents> get-help new-netfirewallrule
NAME
New-NetFirewallRule
SYNOPSIS
Creates a new inbound or outbound firewall rule and adds the rule to the target computer.
SYNTAX
New-NetFirewallRule [-Action <Action>] [-AsJob] [-Authentication <Authentication>] [-CimSession <CimSession[]>]
[-Description <String>] [-Direction <Direction>] [-DynamicTarget <DynamicTransport>] [-EdgeTraversalPolicy
<EdgeTraversal>] [-Enabled <Enabled>] [-Encryption <Encryption>] [-GPOSession <String>] [-Group <String>] [-IcmpType
<String[]>] [-InterfaceAlias <WildcardPattern[]>] [-InterfaceType <InterfaceType>] [-LocalAddress <String[]>]
[-LocalOnlyMapping <Boolean>] [-LocalPort <String[]>]
...

Creating a CSV file to use for starting services on multiple servers

I have created a tsv file to list Servers and Services as follows:
TSV File as follows:
Hostname Services
=========================
Server01 SP4AdminV4,SPTraceV4,SPWriterV4,WAS,W3SVC
Server02 SP4AdminV4,SPTraceV4,SPWriterV4,WAS,W3SVC,SPSearchHostController, OSearch16
PowerShell command
Import-csv C:\ServerServerList.tsv
$Services = $_.Services -Split ','
Start-Service -Computer 'Server01' -Name $Services
I then get the following error:
Start-Service: Cannot Bind Argument to parameter 'Name' because it is an empty string.
At Line:3 char:43
+ Start-Service -Computer $_.Hostname -Name **$Services**
+
+categoryinfo: InvalidData (:) [Start-Service], ParameterBindingValidationException
+FullyQualifiedErrorId: ParameterArgumentValidationErrorEmptyStringNotAllowed,
Microsoft.Powershell.Commands.StartServiceCommand
There are a couple of problems I can spot at a glance. First you don't seem to be assigning $Services correctly. 2nd you Start-Service doesn't have a -ComputerName parameter.
To get around that you can use the use Get-Service in conjunction with Set-Service implicitly using the -InputObject parameter via the pipeline.
Import-csv C:\ServerServerList.tsv -Delimiter "`t" |
ForEach-Object{
$Services = $_.Services -Split ','
Get-Service -Computer $_.HostName -Name $Services
} |
Start-Service
I'm assuming this is a Tab separated file as you described. Also assuming the services are listed in such a way to make the split proper.
The loop sends [System.ServiceProcess.ServiceController] objects down the pipeline. Those are bound to the -InputObject parameter. Internally Start-Service uses the .MachineName property to make the change on the remote system.
Warning: Get/Set-Service doesn't always report errors properly in this kind of scenario. The primary obstacle I've encountered is misleading errors and/or silent failures in cases where the operator account doesn't have access to the remote system.
I'm sure there's easier ways to go about this but, here's what I got:
# import csv and save to variable.
$CSV = Import-Csv -Path 'C:\ServerServerList.tsv'
# Use a foreach loop to iterate throw csv
# one row at a time - assigning the current iteration to $Row.
foreach ($Row in $CSV) {
# Split services into an array
$Services = ($Row.Services -split ',').Trim()
# Invoke the start-service call due to it not
# having it's own -ComputerName parameter to work with.+
Invoke-Command -ScriptBlock {
# Start the service using the newly created array of $Services.
# Note: we must use a *Remote Variable* to pass over our local variable
# by specifying the $using: keyword.
Start-Service -Name $using:Services -PassThru -WhatIf
} -ComputerName $Row.Hostname
}
- This is untested -

Using Invoke-Command, inside the script the methods and properties are not working

I have a file that contains records with the name of a server and the name of its sqlserver instance. I want to read for each record the jobs configured and their status for the last run.
I setup a credential, then define the script to gather information and finally calls Invoke-Command. My problem is that where I expect to find the gathered information I'm just getting the name of properties.
This is my script:
$a = Get-Credential
$scriptBlock={
param($server,$instance) $OFS=","
import-module sqlps
SQLSERVER:
cd SQL\$server\$instance\JobServer\Jobs
foreach($CurJob in (ls)){
write-output "$CurJob.DisplayName`n Last Run: $CurJob.LastRunDate`n Ultima Last Run Otcome:$CurJob.LastRunOutcome`n Next Run: $CurJob.NextRunDate"
}
}
Import-Csv -Header Server, Instance -Delimiter "|" .\servers_with_jobs.txt | %{
write-output "Inovoking with server: $_.Server/$_.Instance"
$JobNumber=Invoke-Command -AsJob -ComputerName $_.Server -Credential $a -ScriptBlock $scriptBlock -ArgumentList $_.Server, $_.Instance
Wait-Job $JobNumber
$JobResult = Receive-Job $JobNumber
$JobResult
}
And when I run, then I get the following:
Inovoking with server: #{Server=server01.company.net; Instance=INST1}.Server/#{Server=server01.company.net; Instance=INST1}.Instance
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
148 Job148 RemoteJob Completed True server01.e... ...
WARNING: The names of some imported commands from the module 'sqlps' include unapproved verbs that might make them less discoverable. To find the commands with
unapproved verbs, run the Import-Module command again with the Verbose parameter. For a list of approved verbs, type Get-Verb.
ReplicationJob1.DisplayName
Last Run: ReplicationJob1.LastRunDate
Ultima Last Run Otcome:ReplicationJob1.LastRunOutcome
Next Run: ReplicationJob1.NextRunDate
ReplicationJob2.DisplayName
Last Run: ReplicationJob2.LastRunDate
Ultima Last Run Otcome:ReplicationJob2.LastRunOutcome
Next Run: ReplicationJob2.NextRunDate
It seems like if the script resolve the name of the job as the ToString method. Even I try to print the type for the $CurJob object but instead it show something like the output of running Get-Member:
PSComputerName : server01.company.net
RunspaceId : 547127ac-ae58-4144-b769-507c6cf18b2e
Module : Microsoft.SqlServer.Smo.dll
Assembly : Microsoft.SqlServer.Smo, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91
TypeHandle : System.RuntimeTypeHandle
BaseType : Microsoft.SqlServer.Management.Smo.Agent.AgentObjectBase
UnderlyingSystemType : Microsoft.SqlServer.Management.Smo.Agent.Job
FullName : Microsoft.SqlServer.Management.Smo.Agent.Job
AssemblyQualifiedName : Microsoft.SqlServer.Management.Smo.Agent.Job, Microsoft.SqlServer.Smo, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91
Namespace : Microsoft.SqlServer.Management.Smo.Agent
GUID : c556994c-df26-35b1-8b70-78bce85a3e25
You need to evaluate parameter calls inside a string evaluation. I didn't test this but I think the example will do the trick.
Example
$a = Get-Credential
$scriptBlock={
param($server,$instance) $OFS=","
import-module sqlps
SQLSERVER:
cd SQL\$server\$instance\JobServer\Jobs
foreach($CurJob in (ls)){
write-output "$($CurJob.DisplayName)`n Last Run: $($CurJob.LastRunDate)`n Ultima Last Run Otcome:$($CurJob.LastRunOutcome)`n Next Run: $($CurJob.NextRunDate)"
}
}
Import-Csv -Header Server, Instance -Delimiter "|" .\servers_with_jobs.txt | %{
write-output "Inovoking with server: $($_.Server)/$($_.Instance)"
$JobNumber=Invoke-Command -AsJob -ComputerName $_.Server -Credential $a -ScriptBlock $scriptBlock -ArgumentList $_.Server, $_.Instance
Wait-Job $JobNumber
$JobResult = Receive-Job $JobNumber
$JobResult
}
A Better Way
Instead of using string evaluations use -f to format a string.
Example
'Inovoking with server: {0}/{1}' -f $_.Server, $_.Instance

Show tabular output in richtextbox

Just installed the PowerShell Studio and I'm trying to view the results of some VMware commands in a richtextbox.
When I run the get-vm, for example, it shows the data fine (I changed the font to Lucida Console- and it looks ok), regular get-vm results in the richtext box:
Name : xxx
Cluster : xxx
ESX Host : esx6
Datastore : xxx
MemoryGB : 8
NumCpu : 2
ProvisionedSpace(GB) : 282
UsedSpace(GB) : 281
OS : Microsoft Windows Server 2008 R2 (64-bit)
But when I try to run (get-vm).Guest.Disks the data not shown good in the richtextbox, it looks like this:
Capacity:192515403776,
FreeSpace:43895230464,
Path:E:\
Capacity:75053920256,
FreeSpace:12630409216,
Path:C:\
when run it in regular powershell console it look like it should:
Volume Capacity(GB) FreeSpace(GB) % FreeSpace
------ ------------ ------------- -----------
E:\ 120 13 11
C:\ 120 15 12
The command line in PowerShell is:
((Get-VM $vm).Guest.disks) | Format-Table #{N="Volume";E={$_.Path}},
#{N="Capacity(GB)";E={[System.Math]::Round($_.CapacityGB)};a="left"},
#{N="FreeSpace(GB)";E={[System.Math]::Round($_.FreeSpaceGB)};a="left"},
#{N="% FreeSpace";E={[math]::Round((100 * ($_.FreeSpacegb / $_.Capacitygb)),0)};a="left"} -auto |
Out-String
the command line in the richtextbox is:
$richtextbox1.AppendText((Get-VM $text).Guest.disks) |
Format-Table #{N="Volume";E={$_.Path}},
#{N="Capacity(GB)";E={[System.Math]::Round($_.CapacityGB)};a="left"},
#{N="FreeSpace(GB)";E={[System.Math]::Round($_.FreeSpaceGB)};a="left"},
#{N="% FreeSpace";E={[math]::Round((100 * ($_.FreeSpacegb / $_.Capacitygb)),0)};a="left"} -auto |
Out-String
How can I get the results like it looks in the PowerShell console wheter is with richtextbox or any other control?
Looks like your richtextbox gives you the results in list format. Pipe your results into the Format-Table cmdlet before piping it into Out-String to enforce table format:
... | Format-Table | Out-String
You need to use one of the following fixed size fonts to display the output:
Consolas
Courier New
Lucida Console

How to find the client name who has logged in to machine?

We are trying to find from which machine a user has taken rdp .
Using "quser" utility we are able to get all the information about logged in user except client name.
Following is the command
function Get-LoggedOnUser
{
param([String[]]$ComputerName = $env:COMPUTERNAME)
$ComputerName | ForEach-Object {
(quser /SERVER:$_) -replace '\s{2,}', ',' |
ConvertFrom-CSV |
Add-Member -MemberType NoteProperty -Name ComputerName -Value $_ -PassThru
}
}
It displays all the information which can be provided in Windows task manager except client Name .
How to get client Name using powershell?
I doubt if WMI has a way to do this. You can check the PSTerminal Services module and it has a Get-TSSession cmdlet which does the same job you are looking for.
http://archive.msdn.microsoft.com/PSTerminalServices
This module uses binary Cassia namespace.
You can use this:
http://gallery.technet.microsoft.com/scriptcenter/0e43993a-895a-4afe-a2b2-045a5146048a
and look for the logged on user with a logon type of RemoteInteractive
You can read that information from the Security eventlog (look for logon type 10):
$username = '...'
$eventID = 4624 # 526 on Server 2003 and earlier
$date = (Get-Date).Date
$pattern = 'logon type:\s+10[\s\S]+source network address:\s+(\S+)'
Get-EventLog Security -InstanceId $eventID -EntryType SuccessAudit `
-After $date -Message '*$username*' `
| ? { $_.Message -match $pattern } `
| % { $matches[1] } `
| select -Unique
Note that on Server 2003 and earlier you need to check for event ID 528 instead of 4624.
References:
http://technet.microsoft.com/en-us/library/cc787567
http://support.microsoft.com/kb/977519

Resources