i'm looking for help in creating a System.Windows.Forms.Textbox that would accept array input and treat it as an array input, rn textbox properties looks something like this:
$boxname.AcceptsReturn = $true
$boxname.MultiLine = $true
$boxname.Autosize = $true
$boxname.AcceptsTab = $false
$boxname.Scrollbars = 'Vertical'
If i enter multiline paths like this:
C:\Windows
C:\Program Files
C:\Temp
$boxname.text is treated like one huge string and won't let my "test-path on each item in array" function work properly, i bound it to this box with .Add_TextChanged, what i'm trying to do is to create a box, that will perform a check that entered paths exist once the box is filled.
Is this even possible without additional parsing ? Maybe i'm missing something.
You need to split the multiline text into separate lines and then loop over these lines to do your checks.
$boxname.Add_TextChanged({
# inside the eventhandler, you can refer to the control object itself using automatic variable $this
$this.Text -split '\r?\n' -ne '' | ForEach-Object {
if (-not(Test-Path -Path $_)) {
# path does not exist, do something..
Write-Host "Path '$_' not found!"
}
}
})
Related
Good day,
Could someone help me with setting some variables after filtering some records from a batchfile in powershell please?
My Batch file (config.bat) looks like this :
---------
set FILENAME=OIMSX4455
set USR=AJ34
set DOCTYPE=WORD
set YEAR=2019
---------
My Powershell code resembles the below although I'm unsure how to set the variables ie (getting the values "WORD" and "AJ34" into $doc and $user
$vars = "c:\temp\config.bat"
get-content $vars | select-string -pattern ("set DOCTYPE=","set USR=")
foreach-object {
$doc=<should equal WORD>
$user=<should equal AJ34>
}
As my config file changes and has several values, I'm only interested in the values what comes after = sign for set DOCTYPE= & set USR= i.e WORD and AJ34 in this example
On another day the config values will change which is why i need to read them again into my powershell variables. Apologies in advance for any mistakes etc
Thanks
The easiest way I think is by using regex on a line-by-line comparison with switch:
$user, $doc = $null
switch -Regex -File 'c:\temp\config.bat' {
'^set USR\s*=\s*(.*)' { $user = $matches[1] }
'^set DOCTYPE\s*=\s*(.*)' { $doc = $matches[1] }
}
Write-Host "user: $user`tdoc: $doc"
Result:
user: AJ34 doc: WORD
I am trying to output a query from a DB to a xlsx but it takes so much time to do this because there about 20,000 records to process, is there a simpler way to do this?
I know there is a way to do it for csv but im trying to avoid that, because if the records had any comma is going to take it as a another column and that would mess with the info
this is my code
$xlsObj = New-Object -ComObject Excel.Application
$xlsObj.DisplayAlerts = $false
$xlsWb = $xlsobj.Workbooks.Add(1)
$xlsObj.Visible = 0 #(visible = 1 / 0 no visible)
$xlsSh = $xlsWb.Worksheets.Add([System.Reflection.Missing]::Value, $xlsWb.Worksheets.Item($xlsWb.Worksheets.Count))
$xlsSh.Name = "QueryResults"
$DataSetTable= $ds.Tables[0]
Write-Output "DATA SET TABLE" $DataSetTable
[Array] $getColumnNames = $DataSetTable.Columns | SELECT *
Write-Output "COLUMN NAMES" $DataSetTable.Rows[0]
[Int] $RowHeader = 1
foreach ($ColH in $getColumnNames)
{
$xlsSh.Cells.item(1, $RowHeader).font.bold = $true
$xlsSh.Cells.item(1, $RowHeader) = $ColH.ColumnName
Write-Output "Nombre de Columna"$ColH.ColumnName
$RowHeader++
}
[Int] $rowData = 2
[Int] $colData = 1
foreach ($rec in $DataSetTable.Rows)
{
foreach ($Coln in $getColumnNames)
{
$xlsSh.Cells.NumberFormat = "#"
$xlsSh.Cells.Item($rowData, $colData) = $rec.$($Coln.ColumnName).ToString()
$ColData++
}
$rowData++; $ColData = 1
}
$xlsRng = $xlsSH.usedRange
[void] $xlsRng.EntireColumn.AutoFit()
#Se elimina la pestaña Sheet1/Hoja1.
$xlsWb.Sheets(1).Delete() #Versión 02
$xlsFile = "directory of the file"
[void] $xlsObj.ActiveWorkbook.SaveAs($xlsFile)
$xlsObj.Quit()
Start-Sleep -Milliseconds 700
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsRng)) {''}
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsSh)) {''}
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsWb)) {''}
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsObj)) {''}
[gc]::collect() | Out-Null
[gc]::WaitForPendingFinalizers() | Out-Null
$oraConn.Close()
I'm trying to avoid [CSV files], because if the records had any comma is going to take it as a another column and that would mess with the info
That's only the case if you try to construct the output format manually. Builtin commands like Export-Csv and ConvertTo-Json will automatically quote the values as necessary:
PS C:\> $customObject = [pscustomobject]#{ID = 1; Name = "Solis, Heber"}
PS C:\> $customObject
ID Name
-- ----
1 Solis, Heber
PS C:\> $customObject |ConvertTo-Csv -NoTypeInformation
"ID","Name"
"1","Solis, Heber"
Notice, in the example above, how:
The string value assigned to $customObject.Name does not contain any quotation marks, but
In the output from ConvertTo-Csv we see values and headers clearly enclosed in quotation marks
PowerShell automatically enumerates the row data when you pipe a [DataTable] instance, so creating a CSV might (depending on the contents) be as simple as:
$ds.Tables[0] |Export-Csv table_out.csv -NoTypeInformation
What if you want TAB-separated values (or any other non-comma separator)?
The *-Csv commands come with a -Delimiter parameter to which you can pass a user-defined separator:
# This produces semicolon-separated values
$data |Export-Csv -Path output.csv -Delimiter ';'
I usually try and refrain from recommending specific modules libraries, but if you insist on writing to XSLX I'd suggest checking out ImportExcel (don't let the name fool you, it does more than import from excel, including exporting and formatting data from PowerShell -> XSLX)
I am building a script to write word documents using PowerShell (Windows 7, PS4, .Net 4, Office 2010.
The script works but it embeds the text at the top of the document. I need to be able to specify the exact location like on page 2.
Here's what I got up to date:
# Opens an MsWord Doc, does a Search-and-Replace and embeds a text file as an object
# To make this work you need in the same folder as this script:
# -A 2-page MsWord file called "Search_and_replace_Target_Template.doc" with:
# -the string <package-name> on page ONE
# -the string <TextFile-Placeholder> on page TWO
# -A textfile called "TextFile.txt"
#
#The script will:
# -replace <package-name> with something on page one
# -embed the text file <package-name> at the top of page one (but I want it to replace <TextFile-Placeholder> on page 2)
#
# CAVEAT: Using MsWord 2010
[String]$MsWordDocTemplateName = "Search_and_replace_Target_Template.doc"
[String]$TextFileName = "TextFile.txt"
[Int]$wdReplaceAll = 2
[Int]$wdFindContinue = 1 #The find operation continues when the beginning or end of the search range is reached.
[Bool]$MatchCase = $False
[Bool]$MatchWholeWord = $true
[Bool]$MatchWildcards = $False
[Bool]$MatchSoundsLike = $False
[Bool]$MatchAllWordForms = $False
[Bool]$Forward = $True
[Int]$Wrap = $wdFindContinue #The find operation continues when the beginning or end of the search range is reached.
[Bool]$Format = $False
[Int]$wdReplaceNone = 0
$objWord = New-Object -ComObject Word.Application
$objWord.Visible = $true #Makes the MsWord
[String]$ScriptDirectory = [System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.path)
[String]$WordDocTemplatePath = "$ScriptDirectory\$MsWordDocTemplateName"
[String]$TextFilePath = "$ScriptDirectory\$TextFileName"
[String]$SaveAsPathNew = Join-Path -Path $ScriptDirectory -ChildPath "${MsWordDocTemplateName}-NEW.doc"
#Open Template with MSWord
Try {
$objDoc = $objWord.Documents.Open($WordDocTemplatePath)
} Catch {
[string]$mainErrorMessage = "$($_.Exception.Message) $($_.ScriptStackTrace) $($_.Exception.InnerException)"
Write-Host $mainErrorMessage -ForegroundColor Red
Start-Sleep -Seconds 7
$objDoc.Close()
$objWord.Quit()
}
$objSelection = $objWord.Selection
$objSelection.Find.Forward = 'TRUE'
$objSelection.Find.MatchWholeWord = 'TRUE'
#Replace <package-name>
[String]$FindText = "<package-name>"
[String]$ReplaceWith = "PackageName_v1"
write-host "replacing [$FindText] :" -NoNewline
$objSelection.Find.Execute($FindText,$MatchCase,$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,$MatchAllWordForms,$Forward,$Wrap,$Format,$ReplaceWith,$wdReplaceAll)
#Embed the text file as an object
[System.IO.FileSystemInfo]$TextFileObj = Get-item $TextFilePath
If ( $(Try {Test-Path $($TextFileObj.FullName).trim() } Catch { $false }) ) {
write-host "Embedding [$TextFileName] :" -NoNewline
[String]$FindText = "<TextFile-Placeholder>"
[String]$ReplaceWith = ""
# $objSelection.Find.Execute($FindText,$MatchCase,$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,$MatchAllWordForms,$Forward,$Wrap,$Format,$ReplaceWith,$wdReplaceAll)
#Need code to create a RANGE to the position of <TextFile-Placeholder>
#Embed file into word doc as an object
#$result = $objSelection.InlineShapes.AddOLEObject($null,$TextFileObj.FullName,$false,$true)
$result = $objSelection.Range[0].InlineShapes.AddOLEObject($null,$TextFileObj.FullName,$false,$true) #works too but does the same
Write-host "Success"
} Else {
Write-Host "[$TextFilePath] does not exist!" -ForegroundColor Red
Start-Sleep -Seconds 5
}
Write-Host "Saving updated word doc to [${MsWordDocTemplateName}-NEW.doc] ***"
Start-Sleep -Seconds 2
#List of formats
$AllSaveFormat = [Enum]::GetNames([microsoft.office.interop.word.WdSaveFormat])
$SaveFormat = $AllSaveFormat
$objDoc.SaveAs([ref]$SaveAsPathNew,[ref]$SaveFormat::wdFormatDocument) #Overwrite if exists
$objDoc.Close()
$objWord.Quit()
$null = [System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$objWord)
[gc]::Collect()
[gc]::WaitForPendingFinalizers()
Remove-Variable objWord
If you have a way to do this in Word 2016 I'll take it too as I'll have to redo it all for office 2016 later on.
UPDATE: Looks like the solution is to create a "Range" where the is located. By default it seems you have one range[0] that represents the whole document.
The closest I came to do this was:
$MyRange = $objSelection.Range[Start:= 0, End:= 7]
But this is not the syntax for PowerShell and it deals only with absolute character positions and not the results of a search for a string.
If I could create a 2nd range maybe this could work:
$objSelection.Range[1].InlineShapes.AddOLEObject($null,$TextFileObj.FullName,$false,$true)
I can't write powershell script for you, but I can describe what you need. You are, indeed, on-track with the Range idea. To get the Range for an entire Word document:
$MyRange = $objDoc.Content
Note that you're free to declare and use as many Range objects as you need. Range is not limited like Selection, of which there can be only one. As long as you don't need the original Range again, go ahead and perform Find on it. If you do need the original Range again, then declare and instantiate a second, instantiating it either as above or, in order to use the original as the starting point:
$MyRange2 = $MyRange.Duplicate
Then use Find on a Range. Note that, when Find is successful the content of the Range is the found term, which puts the "focus" on the correct page. (But will not move the Selection!)
$MyRange.Find.Execute($FindText,$MatchCase,$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,$MatchAllWordForms,$Forward,$Wrap,$Format,$ReplaceWith,$wdReplaceAll)
If you want to test whether Find was successful the Execute method returns a Boolean value (true if Find was successful).
Use something like what follows to insert the file, although I'm not sure OLEObject is the best method for inserting a text file. I'd rather think InsertFile would be more appropriate. An OLEObject requires an OLE Server registered in Windows that supports editing text files; it will be slower and put a field code in the document that will try to update...
$result =$MyRange.InlineShapes.AddOLEObject($null,$TextFileObj.FullName,$false,$true)
I've started learning Powershell and am now stuck after spending several hours on a problem I can find solutions for in multiple languages except Powershell.
I need to place a check against each item in a CheckedListBox that matches any of the values in a semi-colon delimited string named $MLBSVar_SelectedPackages. (eg. $MLBSVar_SelectedPackages = 'packageA;packageB;packageC;') and so on.
I've come up with this line but it doesn't yet work. Please can you help me?
if ($MLBSVar_SelectedPackages -ne $null) {
ForEach ($PackageName in $MLBSVar_SelectedPackages) {
ForEach ($item in $clb_SC_AvailablePackages.Items) {
if ($item -eq $PackageName) {
$clb_SC_AvailablePackages.Item($PackageName).Checked = $true
}
}
}
}
I've also tried .SetItemCheckState([System.Windows.Forms.CheckState]::Checked) in place of .Checked. The (one) issue seems to be getting a handle on the list item in the final section as it seems to be passed as a string rather than object? I have a VBS background and would really appreciate the assistance.
I think what you're looking for is something like the following code. You can use the SetItemChecked() method of the CheckedListBox class to check an item at a particular index. I see that you have attempted to use SetItemCheckState(), but did not make mention of SetItemChecked().
# Import Windows Forms Assembly
Add-Type -AssemblyName System.Windows.Forms;
# Create a Form
$Form = New-Object -TypeName System.Windows.Forms.Form;
# Create a CheckedListBox
$CheckedListBox = New-Object -TypeName System.Windows.Forms.CheckedListBox;
# Add the CheckedListBox to the Form
$Form.Controls.Add($CheckedListBox);
# Widen the CheckedListBox
$CheckedListBox.Width = 350;
$CheckedListBox.Height = 200;
# Add 10 items to the CheckedListBox
$CheckedListBox.Items.AddRange(1..10);
# Clear all existing selections
$CheckedListBox.ClearSelected();
# Define a list of items we want to be checked
$MyArray = 1,2,5,8,9;
# For each item that we want to be checked ...
foreach ($Item in $MyArray) {
# Check it ...
$CheckedListBox.SetItemChecked($CheckedListBox.Items.IndexOf($Item), $true);
}
# Show the form
$Form.ShowDialog();
After running this code, you should be presented with a dialog that looks similar to the following screenshot.
I'm new at writing in powershell but this is what I'm trying to accomplish.
I want to compare the dates of the two excel files to determine if one is newer than the other.
I want to convert a file from csv to xls on a computer that doesn't have excel. Only if the statement above is true, the initial xls file was copied already.
I want to copy the newly converted xls file to another location
If the file is already open it will fail to copy so I want to send out an email alert on success or failure of this operation.
Here is the script that I'm having issues with. The error is "Expressions are only allowed as the first element of a pipeline." I know it's to do with the email operation but I'm at a loss as to how to write this out manually with all those variables included. There are probably more errors but I'm not seeing them now. Thanks for any help, I appreciate it!
$CSV = "C:filename.csv"
$LocalXLS = "C:\filename.xls"
$RemoteXLS = "D:\filename.xls"
$LocalDate = (Get-Item $LocalXLS).LASTWRITETIME
$RemoteDate = (Get-Item $RemoteXLS).LASTWRITETIME
$convert = "D:\CSV Converter\csvcnv.exe"
if ($LocalDate -eq $RemoteDate) {break}
else {
& $convert $CSV $LocalXLS
$FromAddress = "email#address.com"
$ToAddress = "email#address.com"
$MessageSubject = "vague subject"
$SendingServer = "mail.mail.com"
$SMTPMessage = New-Object System.Net.Mail.MailMessage $FromAddress, $ToAddress, $MessageSubject, $MessageBody
$SMTPClient = New-Object System.Net.Mail.SMTPClient $SendingServer
$SendEmailSuccess = $MessageBody = "The copy completed successfully!" | New-Object System.Net.Mail.SMTPClient mail.mail.com $SMTPMessage
$RenamedXLS = {$_.BaseName+(Get-Date -f yyyy-MM-dd)+$_.Extension}
Rename-Item -path $RemoteXLS -newname $RenamedXLS -force -erroraction silentlycontinue
If (!$error)
{ $SendEmailSuccess | copy-item $LocalXLS -destination $RemoteXLS -force }
Else
{$MessageBody = "The copy failed, please make sure the file is closed." | $SMTPClient.Send($SMTPMessage)}
}
You get this error when you are trying to execute an independent block of code from within a pipeline chain.
Just as a different example, imagine this code using jQuery:
$("div").not(".main").console.log(this)
Each dot (.) will chain the array into the next function. In the above function this breaks with console because it's not meant to have any values piped in. If we want to break from our chaining to execute some code (perhaps on objects in the chain - we can do so with each like this:
$("div").not(".main").each(function() {console.log(this)})
The solution is powershell is identical. If you want to run a script against each item in your chain individually, you can use ForEach-Object or it's alias (%).
Imagine you have the following function in Powershell:
$settings | ?{$_.Key -eq 'Environment' } | $_.Value = "Prod"
The last line cannot be executed because it is a script, but we can fix that with ForEach like this:
$settings | ?{$_.Key -eq 'Environment' } | %{ $_.Value = "Prod" }
This error basically happens when you use an expression on the receiving side of the pipeline when it cannot receive the objects from the pipeline.
You would get the error if you do something like this:
$a="test" | $a
or even this:
"test" | $a
I don't know why are trying to pipe everywhere. I would recommend you to learn basics about Powershell pipelining. You are approaching it wrong. Also, I think you can refer to the link below to see how to send mail, should be straight forward without the complications that you have added with the pipes : http://www.searchmarked.com/windows/how-to-send-an-email-using-a-windows-powershell-script.php