Powershell - need to parse a text file and past into email - email

I have a very basic text file containing usernames. I need to be able to search for every section that begins with "User bsmith" (for example), copy all the text that follows and end at the line previous to the next "User XXXX" (User tford, for example below). I will then be pasting the copied text in the body of an email. I have already written the Powershell to create the email and send it. I just need to write the portion to grab the text from the text file listed. Still new to programming, so having a hard time determining what way to search and copy from the .txt file and paste into email body. The svnlog.txt is the file being parsed. I can get all content, but need to be able to search for sections for each "User XXXX" to the next "User XXX".
$olComObject = new-object -comobject outlook.application
$svn = (Get-Content C:\Dev\Powershell\svnlog.txt) -join "`n"
$NewMail = $olComObject.CreateItem(0)
$NewMail.Subject = "Testing Voting Options"
$NewMail.Body = "Please use the attached voting buttons in this email to acknowledge or reject if the following user permissions are correct!" + "`n" + $svn
$NewMail.To = "bsmith#company.com;tford#company.com"
$NewMail.VotingOptions = "Accept;Reject"
$NewMail.Send()
I was thinking of possibly searching for the individual "User XXX" group using the following, but again I am not sure if this is the right route to go or will even work.
$fullsvn = Get-Content "C:\Dev\Powershell\svnlog.txt"
Out-File () "C:\Dev\Powershell\svnlogBSmith.txt"
Text file content format is as follows.
User BSmith
repos
[functionalSpecs:/]
#admins = rw
#dba = r
#hca = rw
#businessAnalysts = rw
#qa = rw
#portal = rw
#intercept = rw
#pmo = rw
[restrictedDocs:/softtek]
#restrictedDocs = rw
Groups
BusinessAnalysts = ajones, pjohnson, ssmith, rjackson,
#pmo = lferguson
User tford
Ok. So I tried TessellatingHeckler's route. However it doesn't seem to be Tagging the lines with the username (in this instance it's TKelems instead of BSmith). Here is my current code and an example of the output I get in the console.
Get-Content C:\Dev\Powershell\svnlog.txt | foreach {
if ($_ -match '^User (.*)') {
$tag = $matches[1]
}
[Management.Automation.PSObject]#{
Tag=$tag
Item=$_
}
}
$TKelems | Where Tag -EQ 'tkelems'
Out-File "C:\Dev\Powershell\svnlogTKelems.txt" -InputObject $TKelems
Output:
Name Value
---- -----
Tag
Item --------------------------------------------------------------------------------
Tag
Item User
Tag
Item tkelems:
Tag
Item
Tag
Item Repos
Tag
Item --------------------
Tag
Item [functionalSpecs:/]
Tag
Item #admins = rw
Tag
Item #eclas = rw
Tag
Item #cfis = rw
Tag
Item #transMgmt = rw
Tag
Item #dba = r
Tag
Item #hca = rw
Tag
Item #businessAnalysts = rw
Tag
Item #qa = rw
Tag
Item #portal = rw
Tag
Item #intercept = rw
Tag
I also forgot to add the content of the Ou-File created in the last line. Not sure what to make of this either. Basically, no tags showing in the console, and the output file shows something totally different (See below):
IsReadOnly : False
IsFixedSize : False
IsSynchronized : False
Keys : {Tag, Item}
Values : {$null, --------------------------------------------------------------------------------}
SyncRoot : System.Object
Count : 2
IsReadOnly : False
IsFixedSize : False
IsSynchronized : False
Keys : {Tag, Item}
Values : {$null, User}
SyncRoot : System.Object
Count : 2

One option:
$textfile = 'c:\testfiles\test.txt'
$Search = 'BSmith'
$Found = (Get-Content $textfile -Raw) -split '(?ms)^(?=User)' -like "User $Search*"
That will read the text file in as a single string, then split it at the lines starting with "User", and filter out the ones that don't match user name in $Search.
You'll need to add error trapping to check for $found being empty, or returning multiple results and handle to suit.

something like this could be a starting point
$usernameregex = 'bsmith'
$found = $false
Get-Content "C:\Dev\Powershell\svnlog.txt" | % {
if (!$found -and $_ -match "^user $username$") {
$found = $true
Write-Output ''
Write-Output $_
} elseif ($found -and $_ -match '^user ') {
if (!($_ -match "^user $username$")) {
$found = $false
} elseif ($_ -match "^user $username$") {
Write-Output ''
Write-Output $_
} else {
Write-Output ''
}
} elseif ($found) {
Write-Output $_
}
} | select -skip 1 | Set-Content "C:\Dev\Powershell\svnlogBSmith.txt"
to check multiple names, change the $usernameregex to this
$usernameregex = (bsmith|tford)

I've seen this "I want lines in a file from [section header] to the next [section header]" before, and mjolinor's answer with the multiline regex is short and effective, but I don't think it's very clear what it's doing and why. And it won't work as nicely on a general array of strings without -joining them first.
As an alternative I quite like the concept of tagging each line, then you can group or filter by the tag. e.g. pull out the username and tag each line with the last-seen username:
gc .\log.txt | foreach {
if ($_ -match '^User (.*)') {
$tag = $matches[1]
}
[pscustomobject]#{
Tag=$tag
Item=$_
}
}
What it does with your file is this:
Tag Item
---- ----
BSmith User BSmith
BSmith repos
BSmith [functionalSpecs:/]
BSmith #admins = rw
BSmith #dba = r
BSmith #hca = rw
BSmith #businessAnalysts = rw
BSmith #qa = rw
BSmith #portal = rw
BSmith #intercept = rw
BSmith #pmo = rw
BSmith [restrictedDocs:/softtek]
BSmith #restrictedDocs = rw
BSmith Groups
BSmith BusinessAnalysts = ajones, pjohnson, ssmith, rjackson,
BSmith #pmo = lferguson
tford User tford
tford
Then you can easily:
$that | Where Tag -eq 'BSmith'
or
$that | group Tag
It could tag lines with any regex match for section delimiters.
[Edit: Added ^ to the regex, suggested by #mjolinor ]

Related

SMLETS: Powershell

We want to generate an SR per row based on the criteria of a CSV file looking like:
SR templete
The additional criterion:
If the SLO countdown is less than 7 days then the due date is always 7 days for the ticket to be due. Otherwise then then countdown is number SLO _Countdown
The support group is always servicedesk
Unless the host_name does not contain "RES" then it is the support group is EITS_HW_Notes and it will be assigned to "custodian".
No matter what an SR is generated even if null.
My difficulty is my lack familiarity with smlets. I am happy to consider generating tickets via email as well. But would like help on how best to do that via powershell. But the code I came up with is below:
`#Prod
#$GLOBAL:smdefaultcomputer = "prodserver"
#Test
$GLOBAL:smdefaultcomputer = "testserver"
Import-Module SMlets
$path = "C:\Temp\Test.csv"
$csv = Import-csv -path $path
#Variable / Class Setup
$srClass = Get-SCSMClass -name System.WorkItem.ServiceRequest
$srprior = Get-SCSMEnumeration -Name ServiceRequestPriorityEnum.Medium
$srurg = Get-SCSMEnumeration -Name ServiceRequestUrgencyEnum.Medium
#$ararea = get-SCSMEnumeration -Name ServiceRequestAreaEnum.Other
$ararea = get-SCSMEnumeration -Name Enum.add3768303064ec18890170ba33cffda
$title = “Title Goes Here”
$descrip = "Description info goes here"
#Service Request Arguements
$srargs = #{
Title = $title;
Urgency = $srurg;
Priority = $srprior;
ID = “SR{0}”;
Area = $ararea;
SupportGroup = "ServiceDesk";
Description = $descrip
}
#Create Service Request
$newServiceRequest = New-SCSMOBject -Class $srClass -PropertyHashtable $srargs -PassThru
#get SR ID of the new object
$SRId = $newServiceRequest.id
#Get Projection & Object for Created Service Request
$srTypeProjection = Get-SCSMTypeProjection -name System.WorkItem.ServiceRequestProjection$
$SRProj = Get-scsmobjectprojection -ProjectionName $srTypeProjection.Name -filter “Id -eq $SRId”
#Set Afffected User
$userClass = Get-SCSMClass -Name Microsoft.AD.UserBase$
$cType = "Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectCriteria"
$cString = "UserName = 'itservicenotifications' and Domain = 'SHERMAN'"
$crit = new-object $cType $cString,$userClass
$user = Get-SCSMObject -criteria $crit
$AffectedUserRel = get-scsmrelationshipclass -name System.WorkItemAffectedUser$
New-SCSMRelationshipObject -RelationShip $AffectedUserRel -Source $newServiceRequest -Target $user -Bulk`
I tried the above code but am running into issues recognizing the column name in the CSV file and am unfamiliar with SMLETS + powershell if statements.
Columns are:
CSV Columns
CSV text with examples is: Columns with examples
Could you paste the CSV columns as text, please? Or, better, a sample CSV with one or two rows (redact any sensitive data).
I would expect a CSV to contain multiple rows - even if yours does not, it's good defensive programming to act as if it does. So the first modification I suggest is:
$path = "C:\Temp\Test.csv"
$csv = Import-csv -path $path
foreach ($Row in $csv)
{
# the rest of your code goes in here
}
I find it helpful while debugging to go step-by-step. If I understand your problem right, it's about building the right hashtable in $srargs to pass to New-SCSMOBject. So the next modification is:
foreach ($Row in $csv)
{
$srClass = Get-SCSMClass -name System.WorkItem.ServiceRequest
# etc
$srargs = #{
Title = $title
Urgency = $srurg
Priority = $srprior
ID = “SR{0}”
Area = $ararea
SupportGroup = "ServiceDesk"
Description = $descrip
}
$srargs # write the hashtable so you can inspect it
# skip the rest of the code for now
}
I understand your question as "how to express the logic of":
support group is always servicedesk
Unless the host_name does not contain "RES"
then the support group is contents of EITS_HW_Notes cell in CSV
and it will be assigned to "custodian"
I can't help you with setting the assignee. But we can rejig the rest of the statement:
if host_name contains "RES"
SupportGroup = servicedesk
else
SupportGroup = contents of EITS_HW_Notes cell
You can code that like this:
foreach ($Row in $csv)
{
$srClass = Get-SCSMClass -name System.WorkItem.ServiceRequest
# etc
if ($Row.host_name -like "*RES*")
{
$SupportGroup = "ServiceDesk"
}
else
{
$SupportGroup = $Row.EITS_HW_Notes
}
$srargs = #{
Title = $title
# etc
SupportGroup = $SupportGroup
Description = $descrip
}
}
Does that get you any closer to your solution?

Powershell Forms - retrieve both values and column names from selected ListView item

I have a working script using Powershell forms and and for convenience I'm retrieving the value from the currently selected item in a listview and copying them to clipboard as follows:
$UIDlist.Add_SelectedIndexChanged({
# Legacy Winforms behaviour can cause an error unless a null check is made
If($UIDlist.SelectedItems -ne $null) {
[string]$ClipText = $this.SelectedItems.SubItems.Text | ForEach-Object{$_ + "`r`n"}
$ClipText | Set-Clipboard
})
Obviously this only outputs the text values of the subitems e.g.
johnsmith01
johnsmith#company.com
\\myserver\johnsmith01
GPO Main Office
Is there an easy way to also get the column names for those values? e.g.
SamAccountName johnsmith01
EmailAddress johnsmith#company.com
HomeDrive \\myserver\johnsmith01
DepoartmentGPO GPO Main Office
There seems to be a Name property of ListView subiteems but I couldnt find the method to populate that when constructing the ListView:
BackColor : Color [Window]
Bounds : {X=0,Y=0,Width=0,Height=0}
Font : [Font: Name=Microsoft Sans Serif, Size=8.25, Units=3, GdiCharSet=0, GdiVerticalFont=False]
ForeColor : Color [WindowText]
Tag :
Text : johnsmith01
Name :
So is there an easy way to retrieve values and the column names for those values when selecting a ListView item?
The name of the listview item is not the column name. It is there in case you want to differentiate what you present to the user (text) and the actual value you will work it behind the scene.
To get the column name, you need to make your own solution.
Here is something that would work.
To be declared somewhere before the Add_SelectedIndexChangedMethod so it doesn't get called every time for nothing.
$ColumnsName = $ListView.Columns.Text
# Used for padded formatting
$LongestColLength = ($ColumnsName | % { $_.length } | Measure-Object -Maximum ).Maximum + 1
$ListView.Items[0].SubItems.text
And the actual $ClipText section
$ClipText = for ($i = 0; $i -lt $ColumnsName.Count ;$i++) {
"{0}: {1} `r`n" -f $ColumnsName[$i].PadRight($LongestColLength, ' '), $this.SelectedItems.SubItems[$i].Text
}
Output
Based on your code, I assumed you wanted to send it to the clipboard immediately with a pretty format but if that is not necessarily the case, I would instead produce a PSObject of what you want so that you can do additional manipulation with ease.
$ClipText = for ($i = 0; $i -lt $ColumnsName.Count ;$i++) {
[PSCustomObject]#{
ColumnName = $ColumnsName[$i]
Text = $this.SelectedItems.SubItems[$i].Text
}
}
Whenever ready, you can still convert your newly formed PSObject to its final format.
User friendly (making use of the longest column length for padding)
$ClipText | % { '{0}: {1}' -f $_.ColumnName.PadRight($LongestColLength,' '),$_.Text}
Machine friendly
$ClipText | ConvertTo-Json
Answer as in Sage's post. In my case I needed the add the following to get the clipboard formatting and output correct:
[string]$ClipText = for ($i = 0; $i -lt $ColumnsName.Count ;$i++) {
'{0}: {1}' -f $ColumnsName[$i].PadRight($LongestColLength, ' '), $this.SelectedItems.SubItems[$i].Text + "`r`n"
}
Without [string] only the last object was in the clipboard and the carriage return/newline was also required
I have PSCX module installed, which modifies the Set-Clipboard command, so YMMV

Loop through paragraphs, Filter by Where-Object Condition

I am trying to parse through a .txt dump of firewall rules (100+). I want to search each rule for a specific parameter (ex. set ips-sensor). If that rule contains the parameter, I want the script to print "value found". If not, I want the script to print "value not found". I have treated each rule as a paragraph and used a delimiter on "next". I cannot get the foreach loop to work. Here is what I have:
$paragraphs = Get-Content C:\firewall_rules.txt -Delimiter "next"
#$line = $paragraphs.Split([string[]]"`r`n", [StringSplitOptions]::None)
$exists = $paragraphs | Where-Object { $_ -like '*set ips-sensor*' }
$noexists = $paragraphs | Where-Object {$_ -notlike '*set ips-sensor*'}
$result = foreach ($line in $paragraphs) {$line.Split([string[]]"`r`n", [StringSplitOptions]::None)}
foreach ($result in $paragraphs)
{
if (?)
{
Write-Host "value found"
}
elseif (?)
{
Write-Host "value not found"
}
}
Here is an example of the exact format:
edit 168
set name "Office Stuff"
set srcintf "Office"
set dstintf "port11"
set srcaddr "overthere"
set internet-service enable
set action accept
set schedule "always"
set logtraffic all
set fsso disable
set nat enable
next
edit 174
set name "My Policy"
set srcintf "Office"
set dstintf "port1"
set srcaddr "overthere"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
set utm-status enable
set logtraffic all
set ips-sensor "default"
next
edit 68
set name "Naughty IPs"
set srcintf "Office"
set dstintf "port1"
set srcaddr "all"
set dstaddr "faraway"
set schedule "always"
set service "ALL"
set logtraffic all
set ips-sensor "default"
set fsso disable
my expected output should be -
value not found
value found
value found
Any help is appreciated.
Nice question, I've added comments on the code so you can follow the thought process.
# Define the word we want to match
$find = 'ips-sensor'
# Set Paragraph counter
$z = 1
# $file will be an array with 3 elements / paragraphs given the example we got
foreach($paragraph in $file)
{
# Set the line counter (this will be reset on each paragraph)
$i = 0
# Set a result hashtable and assume we will not find the word we're looking for
$out = [ordered]#{
Paragraph = $z
Status = 'Not Found'
LineNumber = ''
Line = ''
}
# Split this paragraph on new lines and filter only those elements that are
# not empty strings and loop through it
foreach($line in $paragraph -split '\r?\n' -ne '')
{
# Increment the line counter
$i++
# Self explanatory
if($line -match $find)
{
$out.Status = 'Found'
$out.LineNumber = $i
$out.Line = $line.Trim()
# Break the inner loop, if we are here we don't need to keep looping
# over this array
break
}
}
# Cast a pscustomobject which can be easily manipulated and exported
[pscustomobject]$out
# Increment Paragraph counter
$z++
}
Result
Paragraph Status LineNumber Line
--------- ------ ---------- ----
1 Not Found
2 Found 12 set ips-sensor "default"
3 Found 10 set ips-sensor "default"
If you wanted to find multiple words on your dump file, the code would require a few modifications:
$find = 'ips-sensor', 'naughty' -join '|'
$z = 1
foreach($paragraph in $file)
{
$i = 0
$out = [ordered]#{
Paragraph = $z
Status = 'Not Found'
LineNumber = ''
Line = ''
}
foreach($line in $paragraph -split '\r?\n' -ne '')
{
$i++
if($line -match $find)
{
$out.Status = 'Found'
$out.LineNumber = $i
$out.Line = $line.Trim()
[pscustomobject]$out
}
}
if($out.Status -eq 'Not Found')
{
[pscustomobject]$out
}
$z++
}
Result
Paragraph Status LineNumber Line
--------- ------ ---------- ----
1 Not Found
2 Found 12 set ips-sensor "default"
3 Found 2 set name "Naughty IPs"
3 Found 10 set ips-sensor "default"
Since you've already broken the text up into chunks, they can be passed to a foreach-object and you just simply need to test if the desired string is found.
Get-Content C:\firewall_rules.txt -Delimiter 'next' | ForEach-Object {
if($_ -Match 'set ips-sensor'){
"Value found"
}
else{
"Value not found"
}
}
This produces your desired output.
Value not found
Value found
Value found

check csv for blank fields and write output if exist blank

This is a csv example:
1- 2018-11-07,hostname-184,IP_INFO, 10.2334.40.334, 255.255.255.0,
2 - 2018-11-07,hostname-184,IP_INFO, 334.204.334.68, 255.255.255.0,
3- 2018-11-07,hostname,7.1.79-8,IP_INFO, 142.334.89.3342, 255.255.255.0,
4- 2018-11-07,hostname,7.1.80-7,IP_INFO, 13342.221.334.87, 255.255.255.0,
5- 2018-11-07,hostname-155,IP_INFO, 142.2334.92.212, 255.255.255.0,
6 - 2018-11-07,hostname-184,IP_INFO, , , 1
7- 2018-11-07,hostname-184,IP_INFO, 10.19334.60.3343, 255.255.255.0,
so how can i check if the las two spaces are in blank (like line 6 ) ?
The idea is to use something like this:
$contentdnsparsed = Get-Content $destination_RAW_NAS\DNS_NAS_PARSED_0
For($i=0;$i -lt $contentdnsparsed.count;$i++){
if($contentdnsparsed[$i] -match "running")
{
$Global:MatchDNS = $OK } Else {$Global:MatchDNS = $FAIL }
}
If match "something" in the space 4 and 5 after the "," output = OK else = FAIL.
Thank you guys
Although you give us a rather bad example of a CSV file, you should use the Import-Csv cmdlet.
Because the csv has no headers, you need to supply these with the -Header parameter like below:
$csvContent = Import-Csv -Path "$destination_RAW_NAS\DNS_NAS_PARSED_0" -Header #("Date","HostName", "InfoType","IPAddress","Subnet")
$csvContent | ForEach-Object {
# test for empty IPAddress fields in the CSV
if ([string]::IsNullOrEmpty($_.IPAddress)) {
Write-Host "$($_.HostName) = FAIL" -ForegroundColor Red
# somewhere in your code you have declared the variables $Global:MatchDNS, $FAIL and $OK I guess..
$Global:MatchDNS = $FAIL
}
else {
Write-Host "$($_.HostName) = OK" -ForegroundColor Green
$Global:MatchDNS = $OK
}
}
Hope that helps

Effective permissions on remote share for domain users in Powershell

I searched and read some topics here but I didn't found what I am looking for.
Basically, I want to check the effective permissions for a specific user for several shares, I want a script such as :
$user = Read-Host "Enter username"
$shares = "\\serverABC\share2","\\serverABC\share1"
foreach ($share in $shares)
{
Cmdlet-EffectivePermissions $share
}
Output expected :
\\serverABC\share1
Full Control : No
Traverse folder / execute / file : YEs
List folder / read data : No
...
\\serverABC\share2"
Full Control : No
Traverse folder / execute / file : YEs
List folder / read data : No
...
In fact, I want to do in Powershell exactly the same way that effective permissions Tab.
Does it exist a built-in solution (without importing any modules, add-ins, ...) with .NET Method (GetUserEffectivePermissions) or with Get-ACL?
I'm not aware of a .NET/PowerShell way to do this natively. There is a PowerShell module here that should be able to do what you're looking for, though. After importing that, you should be able to modify your pseudo code to the following:
$user = Read-Host "Enter username"
$shares = "\\serverABC\share2","\\serverABC\share1"
foreach ($share in $shares) {
Get-EffectiveAccess -Path $share -Principal $user -ListAllRights
}
That returns PS objects instead of simple text. If the format isn't to your liking, you can use some of the utility commands to shape it however you like. Here are two examples of doing that:
First, a simple change to original that doesn't return the exact format you mentioned, but it's pretty close:
foreach ($share in $shares) {
$share
Get-EffectiveAccess -Path $share -Principal $user -ListAllRights | ForEach-Object {
"{0}: {1}" -f $_.Permission, $_.Allowed
}
""
}
Next, a more complicated change that formats the output exactly how you were asking (at least I think):
# Go through each FileSystemRights enum name and add them to a hash table if their value is
# a power of 2. This will also keep track of names that share a value, and later those can
# be combined to provide a friendly permission name
$Ht = #{}
foreach ($Name in [System.Enum]::GetNames([System.Security.AccessControl.FileSystemRights])) {
$Value = [System.Security.AccessControl.FileSystemRights]::$Name
if ($Value.value__ -band ($Value.value__ - 1)) {
# Not a power of 2, so ignore this
continue
}
if (-not $Ht.$Value) {
$Ht.$Value = #()
}
$Ht.$Value += $Name
}
# FullControl isn't a power of 2, but it's useful to test for access, so add it manually
$Ht.([System.Security.AccessControl.FileSystemRights]::FullControl) = "FullControl"
function YesNoTest {
param(
[System.Security.AccessControl.FileSystemRights] $EffectiveAccess,
[System.Security.AccessControl.FileSystemRights] $AccessToTest
)
if (($EffectiveAccess -band $AccessToTest) -eq $AccessToTest) {
"Yes"
}
else {
"No"
}
}
$shares | Get-EffectiveAccess -Principal $user | ForEach-Object {
$_.DisplayName
$EffectiveAccess = $_.EffectiveAccess
$Ht.GetEnumerator() | sort { $_.Key.value__ } -Descending | ForEach-Object {
"{0}: {1}" -f ($_.Value -join " / "), (YesNoTest $EffectiveAccess $_.Key)
}
""
}
Note that this won't be completely accurate if you run this against a remote system and the following conditions are met:
The security descriptor contains groups that are local to the remote system, i.e., non domain groups
The user(s) you're checking is a member of one of the local groups