Powershell Balloon Notification Vanishes before showing - c#

Have another head scratcher here, simply put my balloon notification does not work when fired from inside of a function that is inside of a job, the icon shows up for a fraction of a second and then disappears, tried several compinations of -wait -sleep -sleep-start to see if i could delay its "completion" since i feel the function is not running it's full course before ending, it works outside of the job/function, but inside it does not, here is the snippet I am calling
function MakeToolTip
{
[CmdletBinding(SupportsShouldProcess = $true)]
param
(
[Parameter(Mandatory=$true)]
$Text,
[Parameter(Mandatory=$true)]
$Title,
[ValidateSet(‘None’, ‘Info’, ‘Warning’, ‘Error’)]
$Icon = ‘Info’, $Timeout = 10000
)
Add-Type -AssemblyName System.Windows.Forms
if ($script:balloon -eq $null)
{
$script:balloon = New-Object System.Windows.Forms.NotifyIcon
}
$path = Get-Process -id $pid | Select-Object -ExpandProperty Path
$balloon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path)
$balloon.BalloonTipIcon = $Icon
$balloon.BalloonTipText = $Text
$balloon.BalloonTipTitle = $Title
$balloon.Visible = $true
$balloon.ShowBalloonTip($Timeout)
}
This is called with: MakeToolTip "Testing" "VNC Connections" Info
The full code of the script is here, lines 239-269: https://pastebin.com/7Q4gwLJe
I'm considering running the balloon in a separate job? Maybe the pipeline is completing it too quickly because there too much other stuff going on?
EDIT
Finding that when wrapped in a job a balloon notification does not work, very strange behavior
$MakeToolTip =
{
function Makeit {
[CmdletBinding(SupportsShouldProcess = $true)]
param
(
[Parameter(Mandatory=$true)]
$Text,
[Parameter(Mandatory=$true)]
$Title,
[ValidateSet(‘None’, ‘Info’, ‘Warning’, ‘Error’)]
$Icon = ‘Info’, $Timeout = 10000
)
Add-Type -AssemblyName System.Windows.Forms
if ($balloon -eq $null)
{
$balloon = New-Object System.Windows.Forms.NotifyIcon
}
$path = Get-Process -id $pid | Select-Object -ExpandProperty Path
$balloon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path)
$balloon.BalloonTipIcon = $Icon
$balloon.BalloonTipText = $Text
$balloon.BalloonTipTitle = $Title
$balloon.Visible = $true
$balloon.ShowBalloonTip($Timeout)
write-host $Text, $Title
}
}
$balloonjob = Start-Job -ScriptBlock {MakeIt 'Hi' 'There'} -InitializationScript $MakeToolTip -Name "BalloonJob"
Register-ObjectEvent $balloonjob -EventName StateChanged -Action {
if ($eventArgs.JobStateInfo.State -eq [System.Management.Automation.JobState]::Failed)
{
Write-Host "Closing the Balloon"
# This command removes the original job
$sender | Remove-Job -Force
# These commands remove the event registration
$eventSubscriber | Unregister-Event -Force
$eventSubscriber.Action | Remove-Job -Force
}
}

Related

Cannot switch a Button in PowerShell

first of all, I am really new to PowerShell and C# and currently trying out stuff to learn it. I was creating form with the System.Windows.Forms Class and made a small window including two buttonds and a check box. One of the buttons will stay hidden while the other is visible to kinda swap actions as I did not figure another way to do it.
The first Button activates, if the check box is $true, then it triggers the shutdown.exe /s process and changes a variable for the second button. I got it to make the first button work properly, but the second does not seem to do anything at all. Would you mind going over my code to tell me where I made a mistake?
PowerShell.exe -windowstyle hidden {
# Start of code
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
# Declaration of the form
$folderForm = New-Object System.Windows.Forms.Form
$folderForm.StartPosition = 'Manual'
$folderForm.Location = '2267,1107'
# Declaration of the Check Box
$shutdownCheck = New-Object System.Windows.Forms.CheckBox
$shutdownCheck.Location = New-Object System.Drawing.Size(23,23)
$shutdownCheck.Size = '27,27'
$folderForm.Controls.Add($shutdownCheck)
# Declaration of the Shutdown-Button
$shutdownButton = New-Object System.Windows.Forms.Button
$shutdownButton.Text = 'Feierabendmodus aktivieren'
$shutdownButton.Location = '120,23'
$shutdownButton.Size = '160,23'
$folderForm.Controls.Add($shutdownButton)
# Declaration of the Stop-Button
$stopButton = New-Object System.Windows.Forms.Button
$stopButton.Text = 'Sequenz abbrechen!'
$stopButton.Location = '120,23'
$stopButton.Size = '160,23'
$stopButton.Visible = $false
$folderForm.Controls.Add($stopButton)
function TestClick
{
$varX = $true
#Start
if ($shutdownCheck.Checked -eq $true -and $varX -eq $true)
{
# BalloonTip
[reflection.assembly]::loadwithpartialname('System.Windows.Forms')
[reflection.assembly]::loadwithpartialname('System.Drawing')
$notify = new-object system.windows.forms.notifyicon
$notify.Icon = [System.Drawing.SystemIcons]::Information
$notify.visible = $true
$notify.showballoontip(20,'Filler','More Filler',
[system.windows.forms.tooltipicon]::None)
# Shutdown-Prozess
Start-Process shutdown.exe /s
#Stop-Process powershell_ise -force
$shutdownButton.Visible = $false
$stopButton.Visible = $true
$shutdownCheck.Checked = $false
$varX = $false
}
elseif ($shutdownCheck.Checked -eq $false -and $varX -eq $false)
{
# Stop
Start-Process shutdown.exe /a
$shutdownButton.Text = 'Filler'
$stopButton.Visible = $false
$shutdownButton.Visible = $true
$varX = $true
}
}
$shutdownButton.Add_Click({TestClick})
$stopButton.Add_Click({TestClick})
$folderForm.ShowDialog()
# End of code
}
The code below creates a button whose text changes after it's clicked - it's your code with some modifications. Try the following to see if it meets your needs:
PowerShell.exe -windowstyle normal {
# Start of code
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
# Declaration of the form
$folderForm = New-Object System.Windows.Forms.Form
$folderForm.StartPosition = 'Manual'
$folderForm.Location = '267,107'
$folderForm.Size = '315, 200'
$folderForm.Text = 'Shutdown'
# Declaration of the Shutdown-Button
$shutdownButton = New-Object System.Windows.Forms.Button
$shutdownButton.Text = 'Shutdown'
$shutdownButton.Location = '75,23'
$shutdownButton.Size = '150,23'
$folderForm.Controls.Add($shutdownButton)
function TestClick
{
#Start
if ($shutdownButton.Text -eq 'Shutdown')
{
# BalloonTip
[reflection.assembly]::loadwithpartialname('System.Windows.Forms')
[reflection.assembly]::loadwithpartialname('System.Drawing')
$notify = new-object system.windows.forms.notifyicon
$notify.Icon = [System.Drawing.SystemIcons]::Information
$notify.visible = $true
$notify.showballoontip(20,'Shutdown','Shutting down the computer.',
[system.windows.forms.tooltipicon]::None)
#ToDo: replace the code below with desired code
# Shutdown-Prozess
$shutdownButton.Text = 'Abort Shutdown'
#Start-Process shutdown.exe /t 60
Start-Process shutdown.exe /?
}
else
{
# BalloonTip
[reflection.assembly]::loadwithpartialname('System.Windows.Forms')
[reflection.assembly]::loadwithpartialname('System.Drawing')
$notify = new-object system.windows.forms.notifyicon
$notify.Icon = [System.Drawing.SystemIcons]::Information
$notify.visible = $true
$notify.showballoontip(20,'Shutdown Aborted','Shutdown has been aborted.',
[system.windows.forms.tooltipicon]::None)
# Stop
$shutdownButton.Text = 'Shutdown'
Start-Process shutdown.exe /a
}
}
$shutdownButton.Add_Click({TestClick})
$folderForm.ShowDialog()
# End of code
}
Two issues:
$varX is getting set to true every time you click
The TestClick function is only updating a local-scope $varX (only within that function)
# your current code (shortened)
function TestClick
{
$varX = $true ## getting set every button click (in local scope)
if ($shutdownCheck.Checked -eq $true -and $varX -eq $true)
{
$varX = $false ## local scope
}
elseif ($shutdownCheck.Checked -eq $false -and $varX -eq $false)
{
$varX = $true ## local scope
}
}
To fix, try doing something like this instead:
function TestClick
{
if ($shutdownCheck.Checked -eq $true -and $varX -eq $true)
{
$Script:varX = $false ## Scope the variable so the function sets it properly
}
elseif ($shutdownCheck.Checked -eq $false -and $varX -eq $false)
{
$Script:varX = $true ## here too
}
}
$varX = $true ## Move the initial setting outside the function
I suggest skipping the function and global status all together, and simply use the different buttons you created:
# shutdown button
$shutdownButton.Add_Click({
if ($shutdownCheck.Checked) {
Start-Process shutdown.exe /s
$shutdownButton.Visible = $false
$shutdownCheck.Checked = $false
$stopButton.Visible = $true
}
})
# stop button (hidden until shutdown clicked)
$stopButton.Add_Click({
Start-Process shutdown.exe /a
$shutdownButton.Visible = $true
$shutdownButton.Text = 'Stopped'
$stopButton.Visible = $false
})
$folderForm.ShowDialog()
# End of code
edit: Running a function in a script creates a child scope that inherits elements like variables from the script level. When you set a variable inside a function (without setting a scope), then that variable only exists within that function's scope while it's executing, and is not kept. I think it's easier to see in practice:
$A = 'Hello'
$B = 'World'
Function Set-Goodbye {
$B = 'Goodbye'
# The default scope for variables set inside a script
Write-Output "function - local: $A $B"
# Manually check the higher script-level scope
Write-Output "function - script: $A $Script:B"
}
# print output
Set-Goodbye
"script - local: $A $B"
function - local: Hello Goodbye
function - script: Hello World
script - local: Hello World
For a better explanation, check out the about_Scopes page

Read key file located in C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys

One of utility we created generates too many files in C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys. To safely delete these files I want to open each file and exam the key. How can I open these key files in C#? I looked at code at here. The code only returns public key. Can I get more information form these key files?
I ended up using following powershell to get a list of valid key from certificate store. I also added c2319c42033a5ca7f44e731bfd3fa2b5 to the list since I am using IIS service. I delete any key file not in this list.
$MachineCertStores = Get-ChildItem Cert:\LocalMachine
$UserCertStores = Get-ChildItem Cert:\CurrentUser
Foreach ($Store in $MachineCertStores)
{
$path = "Cert:\LocalMachine\" + $($store.Name)
$keys = Get-ChildItem $path
Foreach ($Key in $Keys)
{
$UniqueKeyName = $key.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
if ([string]::IsNullOrWhitespace($UniqueKeyName)){
}else{
write-host $UniqueKeyName
$file = Get-Content "validkey.txt"
$containsWord = $file | %{$_ -match $UniqueKeyName.substring(0,32)}
If($containsWord -contains $true)
{
}else{
$UniqueKeyName.substring(0,32) | Out-File 'validkey.txt' -Append
}
}
}
}
Foreach ($Store in $UserCertStores)
{
$path = "Cert:\CurrentUser\" + $($store.Name)
$keys = Get-ChildItem $path
Foreach ($Key in $Keys)
{
$UniqueKeyName = $key.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
if ([string]::IsNullOrWhitespace($UniqueKeyName)){
}else{
write-host $UniqueKeyName
$file = Get-Content "validkey.txt"
$containsWord = $file | %{$_ -match $UniqueKeyName.substring(0,32)}
If($containsWord -contains $true)
{
}else{
$UniqueKeyName.substring(0,32) | Out-File 'validkey.txt' -Append
}
}
}
}

Why is Get-ChildItem command not reading C Drive from C# Program?

$archive = ("*www*","*Backup*","*inetpub*","*wwwroot*","*archive*","*Archive*","*ARCHIVE*","*WINDOWS*","*Program Files*","*JioMediaShare*","*thumbnails*");
function Log-Message {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true, Position=0)]
[string]$LogMessage
)
Write-Output ("{0} - {1}" -f (Get-Date), $LogMessage)
}
function Exclude-Directories {
Process {
$allowThrough = $true
foreach ($directoryToExclude in $archive) {
$directoryText = "*\" + $directoryToExclude
$childText = "*\" + $directoryToExclude + "\*"
if (($_.FullName -like $directoryText -and $_.PsIsContainer) -or $_.FullName -like $childText) {
$allowThrough = $false
break
}
}
if ($allowThrough) {
return $_
}
}
}
Log-Message "Starting Search"
Get-WMIObject Win32_LogicalDisk | ForEach-Object {
Log-Message $_.DeviceID
Get-ChildItem -Path $_.DeviceID -Include *.jpg, *.png, *.bmp, *.jpeg, *.gif, *.webp -Recurse |
Exclude-Directories |
where {!$_.PSIsContainer} |
Select-Object FullName, Name, BaseName, CreationTime, LastWriteTime, Length |
Export-Csv -NoTypeInformation -Path Images.csv -Encoding UTF8 -Append |
% {$_.Replace('"','')}
}
Log-Message "Search completed."
If I run the above script independently, that script is running without errors and search Images in C:, : and E: drive.
string text = System.IO.File.ReadAllText(#"New-Item.ps1");
using (PowerShell PowerShellInstance = PowerShell.Create()) {
// use "AddScript" to add the contents of a script file to the end of the execution pipeline.
// use "AddCommand" to add individual commands/cmdlets to the end of the execution pipeline.
PowerShellInstance.AddScript(text);
Collection<PSObject> PSOutput = PowerShellInstance.Invoke();
foreach (PSObject outputItem in PSOutput) {
// if null object was dumped to the pipeline during the script then a null
if (outputItem != null) {
Console.WriteLine(outputItem.BaseObject.ToString() + "\n");
}
}
if (PowerShellInstance.Streams.Error.Count > 0) {
Console.Write("Error");
}
Console.ReadKey();
}
When running the PowerShell script in a C# program, it is skipping search in the C: drive.
I think the issue is with -Path $_.DeviceID. As $_.DeviceID is not a full path, but just something like 'C:', PowerShell is trying to interpret what you mean and I think it is defaulting to the working directory for that drive (e.g. the value of $pwd).
One option to get around this is to run Set-Location "$($_.DeviceID)\" before the enumeration and drop the -Path parameter from the call to Get-ChildItem. That is:
Set-Location "$($_.DeviceID)\"
Get-ChildItem -Include *.jpg, *.png, *.bmp, *.jpeg, *.gif, *.webp -Recurse ...

Getting the Keyboard Layout in WPF C#

I need to get the Current Windows Keyboard Layout for my WPF application to map each key correctly and handle AZERTY as well as QWERTY and QWERTZ (and so on...)
I noticed a problem since I am working with a French layout (azerty) but my windows is displayed in English.
I tried various methods to get the layout correctly but without results :
var test1 = InputLanguageManager.Current.CurrentInputLanguage;
and
var test2 = CultureInfo.CurrentCulture;
I tried by having ENG language with AZERTY layout, ENG language with QWERTY layout and FRA language with AZERTY layout but the output from my tests were always different. I could get the language displayed correctly (en-GB) but not the layout.
The following PowerShell1 script declares Get-KeyboardLayoutForPid function which reliably gets the Current Windows Keyboard Layout for any process2.
if ( $null -eq ('Win32Functions.KeyboardLayout' -as [type]) ) {
Add-Type -MemberDefinition #'
[DllImport("user32.dll")]
public static extern IntPtr GetKeyboardLayout(uint thread);
'# -Name KeyboardLayout -Namespace Win32Functions
}
Function Get-KeyboardLayoutForPid {
[cmdletbinding()]
Param (
[parameter(Mandatory=$False, ValueFromPipeline=$False)]
[int]$Id = $PID,
# used formerly for debugging
[parameter(Mandatory=$False, DontShow=$True)]
[switch]$Raw
)
$InstalledInputLanguages = [System.Windows.Forms.InputLanguage]::InstalledInputLanguages
$CurrentInputLanguage = [System.Windows.Forms.InputLanguage]::DefaultInputLanguage # CurrentInputLanguage
$CurrentInputLanguageHKL = $CurrentInputLanguage.Handle # just an assumption
### Write-Verbose ('CurrentInputLanguage: {0}, 0x{1:X8} ({2}), {3}' -f $CurrentInputLanguage.Culture, ($CurrentInputLanguageHKL -band 0xffffffff), $CurrentInputLanguageHKL, $CurrentInputLanguage.LayoutName)
Function GetRealLayoutName ( [IntPtr]$HKL ) {
$regBase = 'Registry::' +
'HKLM\SYSTEM\CurrentControlSet\Control\Keyboard Layouts'
$LayoutHex = '{0:x8}' -f ($hkl -band 0xFFFFFFFF)
if ( ($hkl -band 0xFFFFFFFF) -lt 0 ) {
$auxKeyb = Get-ChildItem -Path $regBase |
Where-Object {
$_.Property -contains 'Layout Id' -and
(Get-ItemPropertyValue -Path "Registry::$($_.Name)" `
-Name 'Layout Id' `
-ErrorAction SilentlyContinue
) -eq $LayoutHex.Substring(2,2).PadLeft(4,'0')
} | Select-Object -ExpandProperty PSChildName
} else {
$auxKeyb = $LayoutHex.Substring(0,4).PadLeft(8,'0')
}
$KbdLayoutName = Get-ItemPropertyValue -Path (
Join-Path -Path $regBase -ChildPath $auxKeyb
) -ErrorAction SilentlyContinue -Name 'Layout Text'
$KbdLayoutName
# Another option: grab localized string from 'Layout Display Name'
} # Function GetRealLayoutName
Function GetKbdLayoutForPid {
Param (
[parameter(Mandatory=$True, ValueFromPipeline=$False)]
[int]$Id,
[parameter(Mandatory=$False, DontShow=$True)]
[string]$Parent = ''
)
$Processes = Get-Process -Id $Id
$Weirds = #('powershell_ise','explorer') # not implemented yet
$allLayouts = foreach ( $Proces in $Processes ) {
$LayoutsExtra = [ordered]#{}
$auxKLIDs = #( for ( $i=0; $i -lt $Proces.Threads.Count; $i++ ) {
$thread = $Proces.Threads[$i]
## The return value is the input locale identifier for the thread:
$LayoutInt = [Win32Functions.KeyboardLayout]::GetKeyboardLayout( $thread.Id )
$LayoutsExtra[$LayoutInt] = $thread.Id
})
Write-Verbose ('{0,6} ({1,6}) {2}: {3}' -f $Proces.Id, $Parent,
$Proces.ProcessName, (($LayoutsExtra.Keys |
Select-Object -Property #{ N='Handl';E={('{0:x8}' -f ($_ -band 0xffffffff))}} |
Select-Object -ExpandProperty Handl) -join ', '))
foreach ( $auxHandle in $LayoutsExtra.Keys ) {
$InstalledInputLanguages | Where-Object { $_.Handle -eq $auxHandle }
}
$ConHost = Get-WmiObject Win32_Process -Filter "Name = 'conhost.exe'"
$isConsoleApp = $ConHost | Where-Object { $_.ParentProcessId -eq $Proces.Id }
if ( $null -ne $isConsoleApp ) {
GetKbdLayoutForPid -Id ($isConsoleApp.ProcessId) -Parent ($Proces.Id -as [string])
}
}
if ( $null -eq $allLayouts ) {
# Write-Verbose ('{0,6} ({1,6}) {2}: {3}' -f $Proces.Id, $Parent, $Proces.ProcessName, '')
} else {
$allLayouts
}
} # GetKbdLayoutForPid
$allLayoutsRaw = GetKbdLayoutForPid -Id $Id
if ( $null -ne $allLayoutsRaw ) {
if ( ([bool]$PSBoundParameters['Raw']) ) {
$allLayoutsRaw
} else {
$retLayouts = $allLayoutsRaw |
Sort-Object -Property Handle -Unique |
Where-Object { $_.Handle -ne $CurrentInputLanguageHKL }
if ( $null -eq $retLayouts ) { $retLayouts = $CurrentInputLanguage }
$RealLayoutName = $retLayouts.Handle |
ForEach-Object { GetRealLayoutName -HKL $_ }
$ProcessAux = Get-Process -Id $Id
$retLayouts | Add-Member -MemberType NoteProperty -Name 'ProcessId' -Value "$Id"
$retLayouts | Add-Member -MemberType NoteProperty -Name 'ProcessName' -Value ($ProcessAux | Select-Object -ExpandProperty ProcessName )
# $retLayouts | Add-Member -MemberType NoteProperty -Name 'WindowTitle' -Value ($ProcessAux | Select-Object -ExpandProperty MainWindowTitle )
$retLayouts | Add-Member -MemberType NoteProperty -Name 'RealLayoutName' -Value ($RealLayoutName -join ';')
$retLayouts
}
}
<#
.Synopsis
Get the current Windows Keyboard Layout for a process.
.Description
Gets the current Windows Keyboard Layout for a process. Identify the process
using -Id parameter.
.Parameter Id
A process Id, e.g.
- Id property of System.Diagnostics.Process instance (Get-Process), or
- ProcessId property (an instance of the Win32_Process WMI class), or
- PID property from "TaskList.exe /FO CSV", …
.Parameter Raw
Parameter -Raw is used merely for debugging.
.Example
Get-KeyboardLayoutForPid
This example shows output for the current process (-Id $PID).
Note that properties RealLayoutName and LayoutName could differ (the latter is wrong; a bug in [System.Windows.Forms.InputLanguage] implementation?)
ProcessId : 2528
ProcessName : powershell
RealLayoutName : United States-International
Culture : cs-CZ
Handle : -268368891
LayoutName : US
.Example
. D:\PShell\tests\Get-KeyboardLayoutForPid.ps1 # activate the function
Get-Process -Name * |
ForEach-Object { Get-KeyboardLayoutForPid -Id $_.Id -Verbose }
This example shows output for each currently running process, unfortunately
even (likely unusable) info about utility/service processes.
The output itself can be empty for most processes, but the verbose stream
shows (hopefully worthwhile) info where current keboard layout is held.
Note different placement of the current keboard layout ID:
- console application (cmd, powershell, ubuntu): conhost
- combined GUI/console app (powershell_ise) : the app itself
- classic GUI apps (notepad, notepad++, …) : the app itself
- advanced GUI apps (iexplore) : Id ≘ tab
- "modern" GUI apps (MicrosoftEdge*) : Id ≟ tab (unclear)
- combined GUI/service app (explorer) : indiscernible
- etc… (this list is incomplete).
For instance, iexplore.exe creates a separate process for each open window
or tab, so their identifying and assigning input languages is an easy task.
On the other side, explorer.exe creates the only process, regardless of
open visible window(s), so they are indistinguishable by techniques used here…
.Example
gps -Name explorer | % { Get-KeyboardLayoutForPid -Id $_.Id } | ft -au
This example shows where the function could fail in a language multifarious environment:
ProcessId ProcessName RealLayoutName Culture Handle LayoutName
--------- ----------- -------------- ------- ------ ----------
5344 explorer Greek (220);US el-GR -266992632 Greek (220)
5344 explorer Greek (220);US cs-CZ 67699717 US
- scenario:
open three different file explorer windows and set their input languages
as follows (their order does not matter):
- 1st window: let default input language (e.g. Czech, in my case),
- 2nd window: set different input language (e.g. US English),
- 3rd window: set different input language (e.g. Greek).
- output:
an array (and note that default input language window isn't listed).
.Inputs
No object can be piped to the function. Use -Id pameter instead,
named or positional.
.Outputs
[System.Windows.Forms.InputLanguage] extended as follows:
note the <…> placeholder
Get-KeyboardLayoutForPid | Get-Member -MemberType Properties
TypeName: System.Windows.Forms.InputLanguage
Name MemberType Definition
---- ---------- ----------
ProcessId NoteProperty string ProcessId=<…>
ProcessName NoteProperty System.String ProcessName=powershell
RealLayoutName NoteProperty string RealLayoutName=<…>
Culture Property cultureinfo Culture {get;}
Handle Property System.IntPtr Handle {get;}
LayoutName Property string LayoutName {get;}
.Notes
To add the `Get-KeyboardLayoutForPid` function to the current scope,
run the script using `.` dot sourcing operator, e.g. as
. D:\PShell\tests\Get-KeyboardLayoutForPid.ps1
Auhor: https://stackoverflow.com/users/3439404/josefz
Created: 2019-11-24
Revisions:
.Link
.Component
P/Invoke
<##>
} # Function Get-KeyboardLayoutForPid
if ( -not ('System.Windows.Forms.InputLanguage' -as [type]) ) {
Add-Type -AssemblyName System.Windows.Forms
}
The Get-KeyboardLayoutForPid function contains a Comment-Based Help placed at the end of its body. I hope that its principle is implementable in C# easy.
The main idea of my approach:
Suppose that the current keyboard layout (CurrentInputLanguage) for a given process is the (user-dependant) default one (DefaultInputLanguage).
Collect keyboard layouts associated with every thread of given process (allLayoutsRaw). Note this trick for a console application: collect keyboard layouts associated with every thread of child conhost process as well.
If there is a keyboard layout different from DefaultInputLanguage in the allLayoutsRaw collection then it's the sought-after one (retLayouts).
1 Does not work in PowerShell Core (pwsh.exe).
2 Might fail for explorer process in a language multifarious environment, see an example of failing scenario in the Comment-Based help.
I am not sure of the ask - whether you want to know the current keyboard layout or you want to set the keyboard layout.
In both cases, InputLanguageManager should help.
You can try setting input language manager to appropriate cultureInfo object.
This should change the keyboard layout for your WPF application
InputLanguageManager.Current.CurrentInputLanguage = new CultureInfo("fr-FR");

Powershell + EWS script to move all text messages to Inbox/SMS subfolder

I'm hoping someone more experienced than me can post a Powershell script example (using the simplest possible method) that will...
Identify all messages that use "Text Message" OR "Multimedia Message" forms... then, move them to the Inbox/SMS subfolder.
Basically, I'm just trying to reproduce what my Outlook 2010 Rule does, except via command line.
If there's a pre-made cmdlet that does this already; with an example of what Im trying to do, that would be ideal.
You should be able to do that with something like this
$MailboxName = $args[0]
## Load Managed API dll
Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.1\Microsoft.Exchange.WebServices.dll"
## Set Exchange Version
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2
## Create Exchange Service Object
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)
## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials
#Credentials Option 1 using UPN for the windows Account
$psCred = Get-Credential
$creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())
$service.Credentials = $creds
#Credentials Option 2
#service.UseDefaultCredentials = $true
## Choose to ignore any SSL Warning issues caused by Self Signed Certificates
## Code From http://poshcode.org/624
## Create a compilation environment
$Provider=New-Object Microsoft.CSharp.CSharpCodeProvider
$Compiler=$Provider.CreateCompiler()
$Params=New-Object System.CodeDom.Compiler.CompilerParameters
$Params.GenerateExecutable=$False
$Params.GenerateInMemory=$True
$Params.IncludeDebugInformation=$False
$Params.ReferencedAssemblies.Add("System.DLL") | Out-Null
$TASource=#'
namespace Local.ToolkitExtensions.Net.CertificatePolicy{
public class TrustAll : System.Net.ICertificatePolicy {
public TrustAll() {
}
public bool CheckValidationResult(System.Net.ServicePoint sp,
System.Security.Cryptography.X509Certificates.X509Certificate cert,
System.Net.WebRequest req, int problem) {
return true;
}
}
}
'#
$TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
$TAAssembly=$TAResults.CompiledAssembly
## We now create an instance of the TrustAll and attach it to the ServicePointManager
$TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
[System.Net.ServicePointManager]::CertificatePolicy=$TrustAll
## end code from http://poshcode.org/624
## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use
#CAS URL Option 1 Autodiscover
$service.AutodiscoverUrl($MailboxName,{$true})
"Using CAS Server : " + $Service.url
#CAS URL Option 2 Hardcoded
#$uri=[system.URI] "https://casservername/ews/exchange.asmx"
#$service.Url = $uri
## Optional section for Exchange Impersonation
#$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
function FolderIdFromPath{
param (
$FolderPath = "$( throw 'Folder Path is a mandatory Parameter' )"
)
process{
## Find and Bind to Folder based on Path
#Define the path to search should be seperated with \
#Bind to the MSGFolder Root
$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)
$tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
#Split the Search path into an array
$fldArray = $FolderPath.Split("\")
#Loop through the Split Array and do a Search for each level of folder
for ($lint = 1; $lint -lt $fldArray.Length; $lint++) {
#Perform search based on the displayname of each folder level
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$fldArray[$lint])
$findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView)
if ($findFolderResults.TotalCount -gt 0){
foreach($folder in $findFolderResults.Folders){
$tfTargetFolder = $folder
}
}
else{
"Error Folder Not Found"
$tfTargetFolder = $null
break
}
}
if($tfTargetFolder -ne $null){
return $tfTargetFolder.Id.UniqueId.ToString()
}
}
}
$TextMessageClass = "IPM.Note.Mobile.SMS"
$MMSMessageClass = "IPM.Note.Mobile.MMS"
$sfItemSearchFilter1 = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass,$TextMessageClass)
$sfItemSearchFilter2 = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass,$MMSMessageClass)
#SearchFilter collection Or
$sfCollection = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::Or);
$sfCollection.Add($sfItemSearchFilter1)
$sfCollection.Add($sfItemSearchFilter2)
# Bind to the Inbox Folder
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
$Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
# Get Folder to Move Item to
$fldId = FolderIdFromPath -FolderPath "\Inbox\SMS"
$SubFolderId = new-object Microsoft.Exchange.WebServices.Data.FolderId($fldId)
$SubFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$SubFolderId)
$Itemids = #()
if($SubFolder -ne $null){
#Define ItemView to retrive just 1000 Items
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$fiItems = $null
do{
$fiItems = $service.FindItems($Inbox.Id,$sfCollection,$ivItemView)
#[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)
foreach($Item in $fiItems.Items){
Write-Host ("Moving " + $Item.Subject)
$Itemids += $Item
}
$ivItemView.Offset += $fiItems.Items.Count
}while($fiItems.MoreAvailable -eq $true)
#Total Items Processed Varible
$nmbProcessed = 0
if($Itemids.Count -gt 0){
write-host ("Move " + $Itemids.Count + " Items")
#Create Collection for Move Batch
$type = ("System.Collections.Generic.List"+'`'+"1") -as "Type"
$type = $type.MakeGenericType("Microsoft.Exchange.WebServices.Data.ItemId" -as "Type")
$BatchItemids = [Activator]::CreateInstance($type)
#Varible to Track BatchSize
$batchSize = 0
foreach($iiID in $Itemids){
$nmbProcessed++
$BatchItemids.Add($iiID.Id)
if($iiID.Size -ne $null){
$batchSize += $iiID.Size
}
#if BatchCount greator then 50 or larger the 10 MB Move Batch
if($BatchItemids.Count -eq 50 -bor $batchSize -gt (10*1MB)){
$Result = $null
$Result = $service.MoveItems($BatchItemids,$SubFolder.Id)
[INT]$collectionCount = 0
[INT]$Rcount = 0
[INT]$Errcount = 0
if($Result -ne $null){
foreach ($res in $Result){
if ($res.Result -eq [Microsoft.Exchange.WebServices.Data.ServiceResult]::Success){
$Rcount++
}
else{
$Errcount++
}
$collectionCount++
}
}
else{
Write-Host -foregroundcolor red ("Move Result Null Exception")
}
Write-host ($Rcount.ToString() + " Items Moved Successfully " + "Total Processed " + $nmbProcessed + " Total Folder Items " + $Itemids.Count)
if($Errcount -gt 0){
Write-Host -foregroundcolor red ($Errcount.ToString() + " Error failed Moved")
}
$BatchItemids.Clear()
$batchSize = 0
}
}
if($BatchItemids.Count -gt 0){
$Result = $service.MoveItems($BatchItemids,$SubFolder.Id)
[INT]$Rcount = 0
[INT]$Errcount = 0
foreach ($res in $Result){
if ($res.Result -eq [Microsoft.Exchange.WebServices.Data.ServiceResult]::Success){
$Rcount++
}
else{
$Errcount++
}
}
Write-host ($Rcount.ToString() + " Items Moved Successfully")
if($Errcount -gt 0){
Write-Host -foregroundcolor red ($Errcount.ToString() + " Error failed Moved")
}
}
}
}
Cheers
Glen

Categories