Unexplained Numeric Output from Powershell Script - powershell

We are working on updating scriptPath attributes in AD. We are updating employees in small groups (approximately 100-200 at a time). For this purpose I have created the following script.
$newScript = "foo.vbs"
# Load Employee List
$employeeList = Get-Content "NAM_logon_EmployeeList.txt"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry( "LDAP://OU=Users,DC=foobar,DC=com" )
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 100
$objSearcher.SearchScope = "Subtree"
$colProplist = "scriptPath", "distinguishedName", "cn"
# Loop through Employee List and update the script value
ForEach ( $employee In $employeeList ) {
$objSearcher.Filter = "(&(objectCategory=person)(objectClass=user)(mail=$employee))"
Foreach ( $colProp in $colPropList ) {
$objSearcher.PropertiesToLoad.Add( $colProp )
}
$colResults = $objSearcher.FindAll()
ForEach ( $user In $colResults ) {
$ntuser = $user.Properties.Item("distinguishedName")
$myUser = $user.Properties.Item("cn")
Script to Pushout the change
$objUser = [ADSI]"LDAP://$($ntuser)"
$objUser.put( "scriptPath", $newScript )
$objUser.SetInfo()
echo "Script Added for $($myUser)"
}
}
The script works fine, but line 18:
$objSearcher.PropertiesToLoad.Add( $colProp )
Outputs numbers to the PowerShell window. One number for each Object and Attribute it adds.
0
1
2
Script Added for Smith, John
4
5
6
Script Added for Doe, Jane
etc.
I don't know why it's doing this. Anyone have any ideas?

It is just output that the command is outputting as it updates the object. A lot of .net objects will do this. If you don't want to see the output do the following:
$null = $objSearcher.PropertiesToLoad.Add( $colProp )

From the documentation found on MSDN:
Return Value
The zero-based index at which the new element is inserted.
So, the numbers you are seeing are the index at which the elements are being added.
For ignoring output like that, I like to do the following:
$objSearcher.PropertiesToLoad.Add( $colProp ) | Out-Null

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?

How to confirm data changes in DataGridView GUI (PowerShell)?

In my case i want to use PS script to build WinForm with some elements including DGV contains of 3 columns (#, Page_Name, shrt). First row need to be template row with default values(1;index;NDX)so i get it from csv-file.My code:
$DataGridView1 = New-Object system.Windows.Forms.DataGridView
$DataGridView1.location = New-Object System.Drawing.Point(20,121)
$DataGridView1.Name = "Page-List"
$DataGridView1.AllowUserToAddRowsChanged = $true
$DataGridView1.AllowUserToAddRows = $true
# $DataGridView1.DataBindings
$DataGridView1.width = 363
$DataGridView1.height = 150
$DataGridView1.ColumnCount = 3
$DataGridView1.ColumnHeadersVisible = $true
$DataGridView1.Columns[0].Name = '#'
$DataGridView1.Columns[0].Width = "40"
$DataGridView1.Columns[1].Name = "Page_Name"
$DataGridView1.Columns[1].Width = "205"
$DataGridView1.Columns[2].Name = "shrt"
$DataGridView1.Columns[2].Width = "75"
$DataGridView1.ReadOnly = $false
$DataGridView1.EditMode = "EditOnEnter"
$templateROW = #(Import-Csv -Delimiter ";" "C:\Users\vkons\OneDrive\Документы\PowerShell\Scripts\test\DGV\index.csv" -Header "#", "Page_Name", "shrt" )
$datatable = ($templateROW + $DataGridView1Rows)
$DataGridView1Data = $datatable
foreach ($Row in $DataGridView1Data){
$DataGridView1.Rows.Add($Row.'#', $Row.Page_Name, $Row.shrt)
}
If user will change Page_Name cells value in first row or will fill Page_Name cell`s in the next row (or rowS) - cells value in column "#" and column "shrt" in edited row(s) would get values programmly by this part code:
$DataGridView1.Add_CellValueChanged({autofill})
Function autofill{
$Numbr = $DataGridView1.CurrentRow.Index+1
$DataGridView1.CurrentRow.Cells[0].value = $Numbr
$Name_Page = $DataGridView1.CurrentRow.Cells[1].value
$preshrt = $Name_Page.ToString($Value) -ireplace "[aoueyi]"
$preshrt = $preshrt.ToUpper()
$shrt = $preshrt[0]+$preshrt[1]+$preshrt[2]
$DataGridView1.CurrentRow.Cells[2].value = $shrt
}
My main target is getting the values of all cells in a column Page_Name as a variable(or as array). So I tried to add next string to the function above.
$Pages = $Row.Page_Name+$DataGridView1.CurrentRow.Cells[1].value
But it returns nothing...(Either $Row.Page_Name)
I can get values of all cells in all rows by
$Page_NamesListRows = #($DataGridView1.Rows.Cells.Value)
(Unfortunately) it returns varriable, consist of all existing cells, not rows array.But when i try
$Page_Names = $DataGridView1.Rows.Cells[1].Value
or
$Page_Names = $DataGridView1.Columns[1].Cells.Value
to get only Names of the Pages, it returns error "cannot get value of a null-array" (either in case with #(...) for right part)
Could anybody answer... Is there any way to get values of all existing cells in "Page_Name" Column.Honestly it doesnt matter would the DGVData automaticly edit by changing cells value event or not.
I need to get only column "Page_Name" values.
In the end I want to apologize for my english language. It has rather poor level. And thank the moderator in advance for corrections my mistakes.
I'm afraid you will have to get the array of values by looping over the rows in the "Page_Name" column.
The last row in the DataGridView will always be the "New" row to create by the user, so you need to skip that one.
Either by doing this:
# -1 to skip the always present empty row at the bottom
$Page_Names = for($row = 0; $row -lt $DataGridView1.Rows.Count - 1; $row++) {
$DataGridView1.Rows[$row].Cells.Item("Page_Name").Value
}
Or something like:
$Page_Names = $DataGridView1.Rows | ForEach-Object {
$data = $_.Cells.Item("Page_Name").Value
if ($data) { $data }
}
Or:
$Page_Names = foreach ($row in $DataGridView1.Rows) {
$row.Cells.Item("Page_Name").Value
}
$Page_Names = $Page_Names[0..($Page_Names.Count - 2)]
The last alternative is costly, because it needs to recreate the entire array when removing the last item
P.S.1 Don't forget to call the Dispose() methods on both the $DataGridview1 object and the main form when done with the GUI
P.S.2 I don't see a property called AllowUserToAddRowsChanged on the DataGridView..
EDIT
To hopefully show better what I mean, here's a demo form with a DataGridView control on it.
The initial data comes from a dummy CSV file with this inside:
"1";"Page number 1";"PN1"
"2";"Page number 2";"PN2"
"3";"Page number 3";"PN3"
"4";"Page number 4";"PN4"
"5";"Page number 5";"PN5"
$form = New-Object System.Windows.Forms.Form
$form.ClientSize = New-Object Drawing.Size 580, 505
$form.text = "20/4/2020 v. 0.1 All Right reserved (c) "
$form.TopMost = $true
$DataGridView1 = New-Object system.Windows.Forms.DataGridView
$DataGridView1.Location = New-Object System.Drawing.Point 20,25
$DataGridView1.Width = 363
$DataGridView1.Height = 150
$DataGridView1.AllowUserToAddRows = $true
$DataGridView1.Name = "Page-List"
$DataGridView1.ColumnCount = 3
$DataGridView1.Columns[0].Name = '#'
$DataGridView1.Columns[0].Width = "40"
$DataGridView1.Columns[1].Name = 'Page_Name'
$DataGridView1.Columns[1].Width = "205"
$DataGridView1.Columns[2].Name = "shrt"
$DataGridView1.Columns[2].Width = "75"
$DataGridView1.AllowUserToAddRows = $true
$DataGridView1.ReadOnly = $false
# Populate the DGV with the data from the CSV
$CsvData = Import-Csv -Path 'D:\Test\TEMPLATE_ROW.csv' -Delimiter ";" -Header "#", "Page_Name", "shrt"
foreach ($row in $CsvData) {
[void]$DataGridView1.Rows.Add($row.'#', $row.Page_Name, $row.shrt)
}
# add the DGV to the form
$form.Controls.Add($DataGridView1)
# show the form and capture the result so you can check if the user cancelled or pressed OK
$result = $form.ShowDialog()
# at this point, you can read the data from the DataGridView column of interest
$Page_Names = for($row = 0; $row -lt $DataGridView1.Rows.Count - 1; $row++) {
$DataGridView1.Rows[$row].Cells.Item("Page_Name").Value
}
# cleanup memory by destroying the DGV and the from
$DataGridView1.Dispose()
$form.Dispose()
In variable $Page_Names you will now have the data from the "Page_Name" column in the DataGridView control.
# show on screen
$Page_Names

How can i split up the results of this hashtable search?

I'm trying to use this to compare my AD NT hashdump with https://haveibeenpwned.com/Passwords hashes.
I'm having trouble with the results grouping multiple usernames with the same password together.
the code:
param(
[Parameter(Mandatory = $true)]
[System.IO.FileInfo] $ADNTHashes,
[Parameter(Mandatory = $true)]
[System.IO.FileInfo] $HashDictionary
)
#>
process {
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
#Declare and fill new hashtable with ADNThashes. Converts to upper case to
$htADNTHashes = #{}
Import-Csv -Delimiter ":" -Path $ADNTHashes -Header "User","Hash" | % {$htADNTHashes[$_.Hash.toUpper()] += #($_.User)}
#Create empty output object
$mrMatchedResults = #()
#Create Filestream reader
$fsHashDictionary = New-Object IO.Filestream $HashDictionary,'Open','Read','Read'
$frHashDictionary = New-Object System.IO.StreamReader($fsHashDictionary)
#Iterate through HashDictionary checking each hash against ADNTHashes
while (($lineHashDictionary = $frHashDictionary.ReadLine()) -ne $null) {
if($htADNTHashes.ContainsKey($lineHashDictionary.Split(":")[0].ToUpper())) {
$foFoundObject = [PSCustomObject]#{
User = $htADNTHashes[$lineHashDictionary.Split(":")[0].ToUpper()]
Frequency = $lineHashDictionary.Split(":")[1]
Hash = $linehashDictionary.Split(":")[0].ToUpper()
}
$mrMatchedResults += $foFoundObject
}
}
$stopwatch.Stop()
Write-Verbose "Function Match-ADHashes completed in $($stopwatch.Elapsed.TotalSeconds) Seconds"
}
end {
$mrMatchedResults
}
}
I tried commenting out | % {$htADNTHashes[$_.Hash.toUpper()] += #($_.User)} which seems to be close, but that somehow removed the Frequency column.
The results look like this:
User Frequency Hash
---- --------- ----
{TestUser2, TestUser3} 20129 H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1
{TestUser1} 1 H2H2H2H2H2H2H2H2H2H2H2H2H2H2H2H2
I would like them separated:
User Frequency Hash
---- --------- ----
{TestUser2} 20129 H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1
{TestUser3} 20129 H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1
{TestUser1} 1 H2H2H2H2H2H2H2H2H2H2H2H2H2H2H2H2
i'm sure this is a simple change, but i have very little powershell experience.
The suggestion to change $FormatEnumerationLimit to -1 is not what i want either, that just fixes the list truncating.
{user1, user2, user3...}
while (($lineHashDictionary = $frHashDictionary.ReadLine()) -ne $null) {
if($htADNTHashes.ContainsKey($lineHashDictionary.Split(":")[0].ToUpper())) {
$Users = $htADNTHashes[$lineHashDictionary.Split(":")[0].ToUpper()]
foreach($User in $Users){
$foFoundObject = [PSCustomObject]#{
User = $User
Frequency = $lineHashDictionary.Split(":")[1]
Hash = $linehashDictionary.Split(":")[0].ToUpper()
}
$mrMatchedResults += $foFoundObject
}
}
}

Take value from SQL server and delete relevant folders

Im new to powershell and would like to
-delete all rows in a sql server DB that have a date older than 10 years
-for every row that is deleted also delete a folder or a hard disk
So for example if I run the query
DELETE FROM [RMS].[dbo].[requests] where date_logged < DATEADD(year, -10, GetDate())
I then thought I could get the lowest request_id and just delete any folders under that number.
So for example if I delete 10 rows with my delete query and then do a select
It would say that the lowest request_id is 11.
I've started below but I'm not sure how to capture that the oldest request_id is?
The SQL would be this ...
SELECT TOP 1 request_id FROM [RMS].[dbo].[requests] order by request_id asc
And also how I would delete any folder "less" than that value.
So if request_id = 11 then I'd need to delete
C:\temp\1
C:\temp\2
C:\temp\3
...
C:\temp\10
Thanks
P
$connectionString = "Data Source=server;Initial Catalog=RMS;Integrated Security=SSPI";
$connection = New-Object System.Data.SqlClient.SqlConnection($connectionString);
$commandR = New-Object System.Data.SqlClient.SqlCommand("DELETE FROM dbo.requests WHERE request_id= 1", $connection);
$commandCount = New-Object System.Data.SqlClient.SqlCommand("select count(*) from requests", $connection);
$connection.Open();
$rowsDeletedR = $commandR.ExecuteNonQuery();
Write-Host "$rowsDeletedR rows deleted";
$rowsCountR = $commandCount.ExecuteScalar();
Write-Host "$rowsCountR rows in requests table";
$connection.Close();
Your task is broad. I intentionally splitted it into smaller pieces. Take a look at this demo and comments.
Since Invoke-SqlCmd is considered harmful (SQL Injection), I use my own function to invoke SQL
function Invoke-Sql(
$ConnectionString,
$Query,
$Parameters
) {
$conn = New-Object System.Data.SqlClient.SqlConnection -ArgumentList $ConnectionString
$cmd = New-Object System.Data.SqlClient.SqlCommand -ArgumentList $Query,$conn
$conn.Open()
if ($Parameters) {
foreach ($arg in $Parameters.GetEnumerator()){
$cmd.Parameters.AddWithValue($arg.Key, $arg.Value) | Out-Null;
}
}
$reader = $cmd.ExecuteReader()
if ($reader.Read()) {
[string[]]$columns = 0..($reader.FieldCount-1) |
% { if ($reader.GetName($_)) { $reader.GetName($_) } else { "(no name $_)" } }
do {
$obj = #{}
0..($reader.FieldCount-1) | % { $obj[$columns[$_]] = $reader[$_] }
[PSCustomObject]$obj
} while ($reader.Read())
}
$reader.Dispose()
$cmd.Dispose()
$conn.Dispose()
}
You need a database table. Since there is no strict schema in question, I assume following, minimal:
$conn = 'Data Source=.;Initial Catalog=Test;Integrated Security=SSPI'
$createTestTable = #'
CREATE TABLE MyRequests
(
RequestId int,
DateLogged datetime
)
'#
Invoke-Sql $conn $createTestTable
There is no sample data, I assume folders named 1, 2, 3, .. 7 and matching records in SQL database:
1..7 | % {
Invoke-Sql $conn 'INSERT MyRequests VALUES (#id,#value)' #{id=$_;value=[DateTime]::Now.AddDays(-$_)}
mkdir $_
}
Table should contain following records (dates may differ):
RequestId DateLogged
----------- -----------------------
1 2018-09-23 14:47:49.113
2 2018-09-22 14:47:49.130
3 2018-09-21 14:47:49.137
4 2018-09-20 14:47:49.140
5 2018-09-19 14:47:49.140
6 2018-09-18 14:47:49.143
7 2018-09-17 14:47:49.147
Then, final solution:
#get deleted id's using OUTPUT clause
$older = Invoke-Sql $conn 'DELETE FROM MyRequests OUTPUT deleted.RequestId WHERE DateLogged<#date' #{date=[DATETime]::Now.AddDays(-4)}
#foreach id in returned set, delete corresponding folder
$older | select -ExpandProperty RequestId | % { rm $_ }

Table Cycling for Powershell

I'm creating a powershell script that I want to read a value (VALUE1) from an excel table (I can convert it to XML if necessary), assign it to a variable($PLACEHOLDER), run the rest of the script, then loop back to the beginning, but instead of reading the original value(VALUE1) I want it to read the value below it(VALUE2) and overwrite $PLACEHOLDER with VALUE2, then re-run the script until it returns a blank value, then I want it to stop. I am insanely new to powershell and it's interaction with excel/xml, so any help would be greatly appreciated. (I'm self-taught, so I don't know TOO much about parameters)
Sample in Terrible Psuedo:
#Initial placeholder value here
$RowNumber = 0
#Start of the loop here, add one to previous value
$RowNumber +1
#Call the value in Column (1), Row ($RowNumber), and assign it to $RowValue
?????? = $RowValue
#Execute the command involving the data value
ECHO "C:/test/temporary/$RowValue"
#Goto the start of the loop.
If you could be so kind, would you please give a quick explanation of the functions that you use (Parameters, what's happening, ect.)
EDIT: If it could detect and skip over blank rows, that would be amazing.
EDIT3: Code for Ansgar
$xl = New-Object -COM 'Excel.Application'
$xl.Visible = $true # set to $false for production
$wb = $xl.Workbooks.Open("C:\Documents and Settings\xe474109\Desktop\EXCEL FILES\testbook2.xlsx")
$ws = $wb.Sheets.Item(1)
$row = $ws.UsedRange.Rows.Count
while ( $ws.Cells.Item($row, 1).Value -ne $null ) {
$PLACEHOLDER = $ws.Cells.Item($row, 1).Value
#
# do stuff with $PLACEHOLDER here
#(I wanted to test this by just printing the $PLACEHOLDER value
$PLACEHOLDER
$row++
}
$wb.Close()
$xl.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xl)
Do you have Excel installed? If so, you can process Excel spreadsheets like this:
$xl = New-Object -COM 'Excel.Application'
$xl.Visible = $true # set to $false for production
$wb = $xl.Workbooks.Open('C:\path\to\your.xlsx')
$ws = $wb.Sheets.Item(1)
$row = $ws.UsedRange.Row
while ( $ws.Cells.Item($row, 1).Value -ne $null ) {
$PLACEHOLDER = $ws.Cells.Item($row, 1).Value
#
# do stuff with $PLACEHOLDER here
#
$row++
}
$wb.Close()
$xl.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xl)
cls
$csv = Import-csv -Path 'C:\test\csvStuff.csv'
foreach ($rec in $csv) {
if ($rec.nameofyourcolumn -ne '') {
& "c:\test\temporary\$($rec.nameofyourcolumn)"
}
}