Category: Uncategorized

One of the next steps I am taking is making the ESP8266 aware of other ESP8266 devices and being able to talk to them.
There is a couple way’s I can do this via UDP either with Broadcast or Multicast and I am still planning to figure out what would work best.
One thing I noticed is that as I enable IGMP I am also unable to continue sending RAW udp packets to the multitask group. Well after about an hour of Diag the issue I found that when my socket recieves a packet it update’s the remote PORT and address to reply back specifically to that client and does not revert the address to continue pointing back at the Multicast group thus net new packets continue to send to the last used address.
I created the following code below to copy the address and restore it after the UDP packet is sent out, resolving the issue.

if (espconn_get_connection_info(udpconn,&premot,0) == ESPCONN_OK)

      if(udpconn->link_cnt) {
         os_printf("EspConn %u HTTPUDP connections:\r\n", udpconn->link_cnt);
         int   i = 0;
         while(i < udpconn->link_cnt) {
            os_printf("%d) " IPSTR ":%u %s\r\n", i,  IP2STR(premot[i].remote_ip), premot[i].remote_port, msg_espconn_state[premot[i].state] );

      //os_printf("Received device find message\n\r"); 
      //os_sprintf(DeviceBuffer, "%s" MACSTR " " IPSTR, pusrdata, MAC2STR(hwaddr), IP2STR(&ipconfig.ip));
      //os_printf("%s\n", DeviceBuffer);

      remot_info premotbackup;
      premotbackup.remote_port  = udpconn->proto.udp->remote_port; 
      premotbackup.remote_ip[0] = udpconn->proto.udp->remote_ip[0];
      premotbackup.remote_ip[1] = udpconn->proto.udp->remote_ip[1];
      premotbackup.remote_ip[2] = udpconn->proto.udp->remote_ip[2];
      premotbackup.remote_ip[3] = udpconn->proto.udp->remote_ip[3];

      unsigned short strlength = os_strlen(DeviceBuffer);
      udpconn->proto.udp->remote_port = premot->remote_port;
      udpconn->proto.udp->remote_ip[0] = premot->remote_ip[0];
      udpconn->proto.udp->remote_ip[1] = premot->remote_ip[1];
      udpconn->proto.udp->remote_ip[2] = premot->remote_ip[2];
      udpconn->proto.udp->remote_ip[3] = premot->remote_ip[3];

      espconn_sendto(udpconn, SSDPResponse, os_strlen(SSDPResponse)); //Send Back Answer

      udpconn->proto.udp->remote_port  = premotbackup.remote_port;
      udpconn->proto.udp->remote_ip[0] = premotbackup.remote_ip[0];
      udpconn->proto.udp->remote_ip[1] = premotbackup.remote_ip[1];
      udpconn->proto.udp->remote_ip[2] = premotbackup.remote_ip[2];
      udpconn->proto.udp->remote_ip[3] = premotbackup.remote_ip[3];

      //os_printf("SSDPResponse sent: %s\n\r", SSDPResponse); 
      //os_printf("SSDPResponse sent to %s\n\r", IP2STR(&premot->remote_ip));  //Cashes

So as many of us have either heard or experienced ransomware in some way or another. I wanted to ensure that our databases .bak files were not getting encrypted by some 3rd party virus and if they were alert us so we don’t start poisoning our backup systems if we were not going to catch it in time to discover we’ve been compromised. Below is a .PS1 powershell script that I use to report on the status of our ‘.BAK’ files. If a virus were to encrypt the files PRTG would alert us on its next check interval.

$Dir = get-childitem "C:\DatabaseBackups\" -recurse
# $Dir |get-member
$List = $Dir | where {$_.extension -eq ".bak"}
#$List | format-table name
#$List | format-table fullname

foreach ($myitem in $List) {
    #Write-Host $myitem.fullname
    $bytes = Get-Content $myitem.fullname -Encoding byte -TotalCount 4

    "<channel>" + $myitem.fullname + "</channel>"
    if ([System.Text.Encoding]::ASCII.GetString($bytes) -eq "TAPE")

Exit 0

Today, About a list of 1000 user’s was plopped on my calendar to audit and remove from our environment if not used. I throw together this script after adding all the users into a single text file and running through them and generated an output that would then fit into our Deprovisioning framework from Caradigm Sentillion, being aware of not disable / deleting active employees. Worked like a charm, I added a time span of not logging in the last 90 days to be safe.

Remove-Variable * -ErrorAction SilentlyContinue; Remove-Module *; $error.Clear(); Clear-Host

$myArray = New-Object System.Collections.ArrayList

foreach($line in get-content "C:\TermList.txt")
        $recipients = $line -split [RegEx]::Escape(",")
        $samAccountName = $recipients[0].trim()

        #Get-ADUser -Identity $samAccountName -Properties enabled, LastLogonDate, LastLogonTimeStamp
        #Get-ADUser -LDAPFilter "(sAMAccountName=$samAccountName)"  | Select-Object -Property samaccountname, enabled, LastLogonDate, LastLogonTimeStamp, msDS-LastSuccessfulInteractiveLogonTime

        $User = Get-ADUser -Identity $samAccountName -Properties enabled, LastLogon, LastLogonDate, LastLogonTimeStamp
        If ($User -ne $Null) 
            if ($User.enabled -eq $false)
                if ($User.LastLogonDate -eq $Null)           
                    Write-Host $User.samAccountName $User.enabled "BLANK"
                    [void] $myArray.Add($User.samAccountName)
                    if ($User.LastLogonDate -gt [datetime]::Today.AddDays(-90))
                        #Write-Host $User.samAccountName $User.enabled $User.LastLogon $User.LastLogonTimeStamp $User.LastLogonDate #User has logged in the last 90days
                        Write-Host $User.samAccountName $User.enabled $User.LastLogon $User.LastLogonTimeStamp $User.LastLogonDate
                        [void] $myArray.Add($User.samAccountName)

foreach ($Employee in $myArray)
    Write-Host "!DELETE_USER|$Employee"

So, Doing some exploring, I was able to remotely launch applications on the remote server on my own personal Winsta. From there I wanted to see if I could automate mouse and keystrokes.
The full window (include children windows) seems to act as one class “Rail_Window” and doesn’t uncover the underlying Control’s / Forms. So I stayed safe and decided to use just keystrokes but FOCUS command in AutoIt is not working with this window class, so I deiced to use a Minimize / restore combo to bring it to the forefront of my desktop then send it keys like below. This may open another possibility to automate server workflow processes without the need for additional software installation. Heck even adding Bitblt I could even capture images and use them to target mouse clicks on the remote machine if I really need it.

Opt("WinDetectHiddenText", 1)   ;0=dont detect, 1=do detect
$hWnd = WinGetHandle("Calculator (Remote)")

If @error Then
    $cmd = WinGetHandle("Administrator: C:\Windows\System32\cmd.exe (Remote)")
    WinSetState($cmd, "", @SW_MINIMIZE )
    WinSetState($cmd, "", @SW_RESTORE )
    ControlSend ( $cmd, "", "", "^c")
    ControlSend ( $cmd, "", "", "calc{enter}")

WinActive("Calculator (Remote)")
$hWnd = WinGetHandle("Calculator (Remote)")
WinSetState($hWnd, "", @SW_MINIMIZE )
WinSetState($hWnd, "", @SW_RESTORE )
ControlSend ( $hWnd, "", "", "587")
ControlSend ( $hWnd, "", "", "!vp")
ControlSend ( $hWnd, "", "", "!vt")

So, This weekend was a much longer weekend and I had a few cycles to work on cleaning up some of my code. I ran into a little bit of a Snag. During the conversion, I needed to add the ability for the ESP8266 to send out a UDP broadcast so other ESP8266 can be “AWARE” of other ESP’s on the network and thus allow them to integrate without hardcoding any information given that they are on the same IGMP messaging group. During the update of the code, there was a bit of an issue. After espconn_igmp_join is called it seem’s the UDP packets no longer want to send to the address like normal without receiving a packet first. I’m going to keep on this and see if I can find a resolution. After reviewing the document I didn’t have any luck finding anything that noted the change in handling outgoing UDP packet information so I’ll have to connect with Espressif to find out.

static int ICACHE_FLASH_ATTR igmp_join(uint32_t ip) {
   if( ip ) 
      ip_addr_t ipgroup;
      int ret;
      ipaddr_aton(MULTICAST_ADDR, & ipgroup);
      os_printf("IGMP Joining: %08x %08x, " , ip, ipgroup.addr);
      espconn_sendto(&HTTPUDPptrespconn, UDPResponse, os_strlen(UDPResponse)); //UDP packet send's fine here
      ret = espconn_igmp_join( (ip_addr_t *) & ip, & ipgroup); //At this point SendTo stops
      espconn_sendto(&HTTPUDPptrespconn, UDPResponse, os_strlen(UDPResponse)); //UDP packet fails to send now.
      if (ret != 0) 
         os_printf( " failed, code %d\n " , ret);
      } else {
         os_printf( " joined\n " );
      return 0;
   os_printf( " Failed to obtain current IP\n " );
   return 1;

I think this is worth noting for me creating a custom solution for Sandboxing an Environment full of domain admins.

Here are the interop’s if you need them, place them in the $dllpath that you choose.


Remove-Variable * -ErrorAction SilentlyContinue; Remove-Module *; $error.Clear(); Clear-Host

#Must run in x32 due to LoadAsm and the MSTSCfile
#Define Butten Click Function

$dllpath = 'C:\mstsc'
$rdp = New-Object AxMSTSCLib.AxMsRdpClient6NotSafeForScripting

[System.Management.Automation.ScriptBlock]$handleconnection = {
    # switch panels
    Write-Host "TADA!"
    #to stop run 
    Unregister-Event thetimer

Function DisplayHelloWorldText{
$rdp.Name = "MyPowerShellRDP" 
$rdp.Enabled = "true" 

$ScreenBounds = [Windows.Forms.SystemInformation]::VirtualScreen

$rdp.DesktopWidth = $ScreenBounds.Width
$rdp.DesktopHeight = $ScreenBounds.Height
$rdp.AdvancedSettings2.DisplayConnectionBar = 'true' 

$rdp.AdvancedSettings2.DisplayConnectionBar = 'true' 
$rdp.AdvancedSettings2.EnableCredSspSupport = "true" 
$rdp.RemoteProgram.RemoteProgramMode = 'true'
$rdp.AdvancedSettings7.SmartSizing = 'true'
$rdp.AdvancedSettings7.PublicMode = 'false'
$rdp.AdvancedSettings7.AuthenticationLevel = 0
register-objectEvent -InputObject $rdp -EventName "OnConnected" -Action { Write-Host "<TIMER>" }
$rdp.ConnectingText = 'Connecting...' 
$rdp.DisconnectedText = "Disconnected" 

$rdp.Server = "PutServerNameHere"
$rdp.UserName = ""
$rdp.AdvancedSettings2.RDPPort = "3389"
$rdp.AdvancedSettings2.ClearTextPassword = "" 

$timer = new-object timers.timer 

$action = {write-host "Timer Elapse Event: $(get-date -Format ‘HH:mm:ss’)"} 
$timer.Interval = 3000 #3 seconds  

#Register-ObjectEvent -InputObject $timer -EventName elapsed –SourceIdentifier thetimer -Action $handleconnection


#to stop run 
#Unregister-Event thetimer


Function SpawnApp{
    $rdp.RemoteProgram.ServerStartProgram("cmd.exe", "", "%SYSTEMROOT%", 'True', "", 'False')
    $rdp.RemoteProgram.ServerStartProgram("calc.exe", "", "%SYSTEMROOT%", 'True', "", 'False')

Add-Type -AssemblyName System.Windows.Forms
$Form = New-Object system.Windows.Forms.Form
$Form.Text = "My simple Form"
$Label = New-Object System.Windows.Forms.Label
$Label.Text = "This form is very simple."
$Label.AutoSize = $True

#Create button
$Button1 = new-object System.Windows.Forms.Button
$Button1.Location = new-object System.Drawing.Size(50, 50)
$Button1.Size = new-object System.Drawing.Size(80,20)
$Button1.Text = "Button"

#Create button
$Button2 = new-object System.Windows.Forms.Button
$Button2.Location = new-object System.Drawing.Size(50, 70)
$Button2.Size = new-object System.Drawing.Size(80,20)
$Button2.Text = "Spawn"

$rdp.Dock = 'Fill' 



Creating a Virtualized application remotely is a pretty neat feature for locking down user’s that pretty much require Domain Admin 24/7. With this you can help streamline access from a Domain Admin user to a remote server sandbox and launch chrome remotely, I tested this on 2008R2 and 2012R2 and works amazing. Some minor Hurdles to look out for are: Objects marked Safe for scripting do not allow remotely launching applications on behalf of the user so you much use the “Unsafe for scripting” object. Second, you must only call “ServerStartProgram” after you connect to the server, not before. third, You much ensure fDisabledAllowList is set to “1” or you explicitly allow such applications on the server through an approved registry list. Check MSDN for examples/guidance. I tried to do it remotely with the AX7 Sub Routine below however it only applies to x86 and required .Net 4.0 to edit the correct key remotely but should be pretty simple. The key update does not require a reboot. Enjoy!

Private Sub AX7()
        RegKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, "\\" & Form1.remotePcComboBox.Text).OpenSubKey("SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\TSAppAllowList", True)
        If RegKey.GetValue("fDisabledAllowList") <> 1 Then
            RegKey.SetValue("fDisabledAllowList", "1")
        End If
    Catch ex As Exception
    End Try
    AxMsRdpClient71.DesktopWidth = SystemInformation.VirtualScreen.Width
    AxMsRdpClient71.DesktopHeight = SystemInformation.VirtualScreen.Height
    AxMsRdpClient71.RemoteProgram2.RemoteProgramMode = True
    AxMsRdpClient71.AdvancedSettings7.AuthenticationLevel = 0
    AxMsRdpClient71.AdvancedSettings7.SmartSizing = True
    AxMsRdpClient71.AdvancedSettings7.PublicMode = False
    AxMsRdpClient71.AdvancedSettings7.ClearTextPassword = Form1.TextBox4.Text

    AxMsRdpClient71.Server = Form1.remotePcComboBox.Text
    AxMsRdpClient71.UserName = Form1.TextBox3.Text
    AxMsRdpClient71.FullScreen = True
End Sub

Private Sub MyRDP_OnConnected() Handles AxMsRdpClient71.OnConnected
    Debug.WriteLine("RDPEVENT: OnConnected")
        AxMsRdpClient71.RemoteProgram2.ServerStartProgram("C:\Program Files (x86)\Google\Chrome\Application\chrome.exe", "", "%SYSTEMROOT%", True, "", False)
    Catch ex As Exception
    End Try

End Sub

Private Sub MyRDP_OnDisconnected1() Handles AxMsRdpClient71.OnDisconnected
    Debug.WriteLine("RDPEVENT: OnDisconnected")
End Sub

Private Sub MyRDP_OnDisconnected1(ByVal discReason As Integer) Handles MyRDP.OnDisconnected
    Debug.WriteLine("RDPEVENT: OnDisconnected")
End Sub

Private Sub MyRDP_OnLoginComplete() Handles AxMsRdpClient71.OnLoginComplete
    Debug.WriteLine("RDPEVENT: OnLogonComplete")
End Sub

Now in the picture below, I’m running windows 7 and mstscax.dll contains a few controls to add. The control’s marked a distribable are for safe scripting and launch remote applications will not work, You need to use the “Microsoft RDP Client Control – version 8” without the “(redistributable)” in it’s name.

Other interesting Sources:

So currently I’ve been on this workday implementation team for a few months now. Because of the environment I am in we are currently a Powershell shop.
The task I am assigned with is basically conversion of the workday feed to integrate with our current Provisioning / PIM system along with uploading the generated data back into the workday system itself. One of the fields that need to be updated within workday is letting workday know what email was assigned in ActiveDirectory.
I basically landed on this…
It worked Amazing, However, the API it uses to upload an email to workday is via the “Maintain_Contact_Information_for_Person_Event_Request” API call. This will lock the Employee’s record to some degree so Workday has asked that I use “Change_Work_Contact_Information_Request” instead to update the User’s email information. I generated the following script below to handle the new API call from the workday library. Don’t forget you also need to add the “Set-WorkDayWorkerEmailEx” library in the PSM1 file like below to chain the new functionality from the library.

function WriteXmlToScreenHere ([xml]$xml)
    $StringWriter = New-Object System.IO.StringWriter;
    $XmlWriter = New-Object System.Xml.XmlTextWriter $StringWriter;
    $XmlWriter.Formatting = "indented";
    Write-Output $StringWriter.ToString();

function Set-WorkdayWorkerEmailEx {
    Sets a Worker's email in Workday.

    Sets a Worker's email in Workday.

    The Worker's Id at Workday.

    The type of ID that the WorkerId represents. Valid values
    are 'WID', 'Contingent_Worker_ID' and 'Employee_ID'.

    Sets the Workday primary Work email for a Worker. This cmdlet does not
    currently support other email types.

.PARAMETER Human_ResourcesUri
    Human_Resources Endpoint Uri for the request. If not provided, the value
    stored with Set-WorkdayEndpoint -Endpoint Human_Resources is used.

    Username used to authenticate with Workday. If empty, the value stored
    using Set-WorkdayCredential will be used.

    Password used to authenticate with Workday. If empty, the value stored
    using Set-WorkdayCredential will be used.


Set-WorkdayWorkerEmailEx -WorkerId 123 -WorkEmail


    param (
        [Parameter(Mandatory = $true,
        #[ValidatePattern ('^[a-fA-F0-9\-]{1,32}$')]
        [ValidateSet('WID', 'Contingent_Worker_ID', 'Employee_ID')]
        [string]$WorkerType = 'Employee_ID',
        [Parameter(Mandatory = $true)]
        [string]$UsageType = 'WORK',

    if ([string]::IsNullOrWhiteSpace($Human_ResourcesUri)) { $Human_ResourcesUri = $WorkdayConfiguration.Endpoints['Human_Resources'] }

$request = [xml]@'
<bsvc:Maintain_Contact_Information_for_Person_Event_Request bsvc:Add_Only="false" xmlns:bsvc="urn:com.workday/bsvc">
            <bsvc:Comment>Email set by Set-WorkdayWorkerEmail</bsvc:Comment>
            <bsvc:ID bsvc:type="Employee_ID">Employee_ID</bsvc:ID>
                <bsvc:Usage_Data bsvc:Public="true">
                    <bsvc:Type_Data bsvc:Primary="true">
                        <bsvc:ID bsvc:type="Communication_Usage_Type_ID">WORK</bsvc:ID>

$request = [xml]@'
<bsvc:Change_Work_Contact_Information_Request xmlns:bsvc="urn:com.workday/bsvc" bsvc:version="v30.0">

            <bsvc:ID bsvc:type="Employee_ID">Employee_ID</bsvc:ID>
            <bsvc:Person_Email_Information_Data bsvc:Replace_All="true">
                <bsvc:Email_Information_Data >
                    <bsvc:Usage_Data bsvc:Public="true">
                        <bsvc:Type_Data bsvc:Primary="true">
                                <bsvc:ID bsvc:type="Communication_Usage_Type_ID">WORK</bsvc:ID>

    $request.Change_Work_Contact_Information_Request.Change_Work_Contact_Information_Data.Person_Reference.ID.InnerText = $WorkerId
    if ($WorkerType -eq 'Contingent_Worker_ID') {
        $request.Change_Work_Contact_Information_Request.Change_Work_Contact_Information_Data.Person_Reference.ID.type = 'Contingent_Worker_ID'
    } elseif ($WorkerType -eq 'WID') {
        $request.Change_Work_Contact_Information_Request.Change_Work_Contact_Information_Data.Person_Reference.ID.type = 'WID'

    $request.Change_Work_Contact_Information_Request.Change_Work_Contact_Information_Data.Person_Contact_Information_Data.Person_Email_Information_Data.Email_Information_Data.Email_Data.Email_Address = $Email
    $request.Change_Work_Contact_Information_Request.Change_Work_Contact_Information_Data.Event_Effective_Date = (Get-Date).ToString( 'yyyy-MM-dd' )
    $request.Change_Work_Contact_Information_Request.Change_Work_Contact_Information_Data.Person_Contact_Information_Data.Person_Email_Information_Data.Email_Information_Data.Usage_Data.Type_Data.Type_Reference.ID.'#text' = $UsageType

    $request.Change_Work_Contact_Information_Request.Change_Work_Contact_Information_Data.Person_Contact_Information_Data.Person_Email_Information_Data.Email_Information_Data.Usage_Data.Type_Data.Primary =
        if ($Secondary) {'0'} else {'1'}
    $request.Change_Work_Contact_Information_Request.Change_Work_Contact_Information_Data.Person_Contact_Information_Data.Person_Email_Information_Data.Email_Information_Data.Usage_Data.Public =
        if ($Private) {'0'} else {'1'}

    #Write-Output "Sending: " $request
    #Write-Host "Sending: " 
    Invoke-WorkdayRequest -Request $request -Uri $Human_ResourcesUri -Username:$Username -Password:$Password | Write-Output

Today I ended up working on a task to convert some of my .NET code into more of a scriptable powershell form. I took the interop “itextsharp.dll” below and used the following code to generate a PDF will the fields filled in. The PDF was created using Acrobat pro and I added the functionality to Sign the Document with a certificate (Which also locks the document) so we can verify who signed off on the termination procedure. Once the user is Terminated they will show in a text feed which this script will parse and generated a PDF for the owner to fill out that such work has been executed and complied. They then sign the PDF and file with Human resources showing the user’s access was correctly decommissioned.

You may get a copy of ITextSharp here

In order to obtain a Adobe’s Signer’s cert to sign the PDF with, Follow these steps here.

And the signing of the form, I currently don’t have an signature set but once it is it will print your scanned signature in onto the document.

Remove-Variable * -ErrorAction SilentlyContinue; Remove-Module *; $error.Clear(); Clear-Host
Add-Type -Path 'C:\itextsharp.dll'

foreach($line in get-content "\\MyServer\c$\Feed.txt")
        $recipients = $line -split [RegEx]::Escape("|")
        $samAccountName = $recipients[1].trim()
        $DeptCode = $recipients[2].trim()
        $JobCode = $recipients[3].trim()
        $FirstName = $recipients[4].trim()
        $MiddleInital = $recipients[5].trim()
        $LastName = $recipients[6].trim()
        $Director = $recipients[7].trim()
        $Hiredate = $recipients[8].trim()
        $Termed = $recipients[9].trim()

        $fullname = $FirstName
        if ($MiddleInital -ne "")
            $fullname += " $MiddleInital"
        $fullname += " $LastName"

        $TodaysDate = Get-Date -UFormat "%Y%m%d"
        #$User = Get-ADUser -LDAPFilter "(sAMAccountName=$samAccountName)"  | Select-Object -Property samaccountname,enabled
        #If ($User -ne $Null) 
        if ($TodaysDate -eq $Termed)
            Write-Host $samAccountName.PadRight(10) "`t" $fullname.ToString().PadRight(25) "`t" $Hiredate $Termed
            $OutputFile = 'C:\AutoTerm\' + $samAccountName + ".pdf" #The function will add to the PDF to create the complete filename
            $reader = New-Object iTextSharp.text.pdf.pdfreader -ArgumentList 'C:\term_checklist_template.pdf'

            $stamper = New-Object iTextSharp.text.pdf.PdfStamper($reader, [System.IO.File]::Create($OutputFile))
            $stamper.AcroFields.SetField('DATEPRINTED', '') #This is auto populated.
            $stamper.AcroFields.SetField('DATETERMED', $Termed)
            $stamper.AcroFields.SetField('ENAME', $fullname)
            $stamper.AcroFields.SetField('EID', $samAccountName)

            foreach($line in get-content "\\MyServer\current deptcenter jobcodeDesc.txt")
                $Lines = $line -split [RegEx]::Escape("|")
                $CurrentDeptCode = $Lines[1].trim()
                $CurrentDeptDesc = $Lines[2].trim()
                $CurrentJobCode = $Lines[3].trim()
                $CurrentJobDesc = $Lines[4].trim()
                if (($CurrentDeptCode -like $DeptCode) -and ($CurrentJobCode -like $JobCode))
                    Write-Host $CurrentDeptDesc $CurrentJobDesc

            $stamper.AcroFields.SetField('ETITLE', $CurrentJobDesc)
            $stamper.AcroFields.SetField('EDEPARTMENT', $CurrentDeptDesc)


            Send-MailMessage -From "Termination <PowershellPreProcessor@Domain.Com>" -To "Deprovisioning Team <DePro@Domain.Com>", "Deprovisioning Director <>" -Subject "Terminated Report Checklist" -Body "Please fill out and send to HR" -Attachments $OutputFile -Priority High -dno onSuccess, onFailure -SmtpServer ""


for ($page = 1; $page -le $reader.NumberOfPages; $page++)
  # extract a page and split it into lines
  $text = [iTextSharp.text.pdf.parser.PdfTextExtractor]::GetTextFromPage($reader,$page).Split([char]0x000A)

  Write-Host "Page $($page) contains $($text.Length) lines. This is line 5:"
  Write-Host $text[4]

  #foreach ($line in $text)
  #  any tasks


So this was a piece that looked waaaayy more complicated then it is. So at first glance, it’s like Yikes! A lot of notes. So, Let’s take a look. First thing’s fist. “Moderato”, Play at Moderate speed, It seems this speed should feel pretty natural -The Wiki here seems to explain it to be at approx. 108–120 bpm Next, Key Signature. 1 Flat, So it’s F Major or D Minor. Not only does the start and end of this passage end in D, It also is made in Broke time 1600-1749 also a very strong indicator of no Sustain pedal or Major Scales so the song can be concluded to be written in D Minor and REMEMBER ALWAYS HARMONIC MINOR BY DEFAULT!!! (Sharp 7 and in this case C => C#). One thing to highlight is that ‘C’ is not in the Key of D Minor but is a secondary dominate to ‘F’.

Chord Progression -> D, A, D, C, F, C, D, A, D, A, C, F, Trill, Trill, D

This progress is dept for the next three passages exactly, I will try to find the full song and post it here when I stumble upon it.