Tuesday, February 6, 2018

Powershell Start-Job example

Example Start-Job usage

Example of using Start-Job in your script.

Job file (\\path\job-example2.ps1)

This is the script that will be called multiple times. Wanted to illustrate calling a file with parameters. It's a trival example, but wanted to show different output in the job results.
param(
    $runTask = $false,
    $action
)
if($runTask){
    $message = $action
    switch($action){
        "0"{
            $message = $message + ":zero"
        }
        "1"{
            $message = $message + ":one"
        }
        "2"{
            $message = $message + ":two"
        }
        "3"{
            $message = $message + ":three"
        }
        "4"{
            $message = $message + ":four"
        }
        "5"{
            $message = $message + ":five"
        }
        default{
            $message = $message + ":don't know"
        }
    }
}else{
    write-output "did not run"
}
Write-Output $message

Tasker file (\\path\job-example1.ps1)

This will create multiple jobs. Each job will be assigned a numeric iteration of the loop value as the name. We'll wait for all the jobs to finish and then get the output of each job using the name we assigned.
$scriptfile = $MyInvocation.MyCommand.Path
$scriptPath = split-path -parent $scriptfile

for($i=0;$i -le 5;$i++){
    $arguments = @($true,"$i")
    start-job -filepath "$scriptPath\job-example2.ps1" -ArgumentList $arguments -name $i
    sleep -Seconds 1
}

do{
    $state = get-job | ?{$_.state -eq 'Running'}
    sleep -second 1
}while($state -ne $null)

for($i=0;$i -le 5;$i++){
    receive-job -name $i
}


Expected output:


Id     Name            PSJobTypeName   State         HasMoreData     Location             Command                  
--     ----            -------------   -----         -----------     --------             -------                  
1      0               BackgroundJob   Running       True            localhost            param(...                
3      1               BackgroundJob   Running       True            localhost            param(...                
5      2               BackgroundJob   Running       True            localhost            param(...                
7      3               BackgroundJob   Running       True            localhost            param(...                
9      4               BackgroundJob   Running       True            localhost            param(...                
11     5               BackgroundJob   Running       True            localhost            param(...                
0:zero
1:one
2:two
3:three
4:four
5:five





Friday, February 2, 2018

ColdFusion and AWS SNS

How to tie ColdFusion 11 server with AWS SNS.
Let's say you have an AWS SNS topic that sends a lot of information. And you're overwhelmed and you want a more streamlined solution such as a dashboard with database backend. And for the sake of this scenario, we'll leave AWS Gateway API and Lambda out of it. Because you already have a ColdFusion just sitting around.

Step 1: Write the CFC files
  1. Go to your website's physical directory and create a new folder called "rest". You don't have to call it that, but I do just for example sake. 
  2. Create two files:
    1. application.cfc
    2. sns.cfc
  3. application.cfc
    component output="false"
    {
         this.name = "my-rest";
         this.applicationTimeout = createTimespan(0,1,0,0);
         this.datasource = "myDB";
    
         this.restSettings.skipCFCWithError = true;
    
         public boolean function onRequestStart()
         {
              request._body = toString(getHttpRequestData().content);
              return true;
         }
    
         public boolean function onRequest()
         {
              return true;
         }
    }
    
    1. It is important that you set "request._body" here. Otherwise, you won't see the content. 
    2. You can put any other functions here to override the one from parent's Application.cfc
  4. sns.cfc
    <cfcomponent rest="true" restpath="sns">
     <cffunction name="postSNSTopic" access="remote" httpMethod="POST" returntype="string" produces="application/json">
      <cfargument name="body" type="string" restArgumentSource="body"/>
      <cfscript>
       response = "y";
    
       messageType = getHttpRequestData().headers["x-amz-sns-message-type"];
       messageID   = getHttpRequestData().headers["x-amz-sns-message-id"];
       topicARN    = getHttpRequestData().headers["x-amz-sns-topic-arn"];
       subject     = "None";
       messageJson = "";
       content     = "";
    
       switch(messageType){
        case "SubscriptionConfirmation":
         //Enter subscription action here
         break;
        case "Notification":
         //Enter notification action here
         break;
        default:
         response = "n";
       }
       
       if(response IS "y"){
        content = getHttpRequestData().content;
        contentLen = len(content);
        if(contentLen GT 0){
         contentStruct = deserializeJson(toString(content));
         subject = contentStruct["Subject"];
         if(len(subject) EQ 0){
          subject = "None";
         }
         messageJson = contentStruct["Message"];
         messageLen  = len(messageJson);
         if(messageLen EQ 0){
          messageJson = "No Content";
         }
        }else{
         content = "No Content";
         //TroubleShooting
         //headers = getHttpRequestData().headers;
         //for(item in headers){
          //content = content & "." & item & ":" & getHttpRequestData().headers[item];
         //}
        }
       }
      </cfscript>
      <cfif response EQ "y">
       <cfquery name = "sns_insert" datasource="mydb">
        insert into sns_listener(messageType, messageID, topicARN, subject, messageJSON, content, timestamp) values (
         <cfqueryParam value=#messageType# cfSqlType="cf_sql_varchar" maxlength="100"/>,
         <cfqueryParam value=#messageID# cfSqlType="cf_sql_varchar" maxlength="100"/>,
         <cfqueryParam value=#topicARN# cfSqlType="cf_sql_varchar" maxlength="100"/>,
         <cfqueryParam value=#subject# cfSqlType="cf_sql_varchar" maxlength="100"/>,
         <cfqueryParam value=#messageJson# cfSqlType="cf_sql_clob" maxlength="100"/>,
         <cfqueryParam value=#content# cfSqlType="cf_sql_clob" maxlength="100"/>,
         sysdate)
       </cfquery>
      <cfelse>
       <cfquery name = "sns_insert" datasource="mydb">
        insert into sns_listener(messageType, timestamp) values ('error',sysdate)
       </cfquery>
      </cfif>
     </cffunction>
    </cfcomponent>
    
  5. Details about HTTP Header and JSON content from SNS message can be found here.
Step 2: Configuring REST Service in ColdFusion 11
  1. Log into ColdFusion Administrator
  2. Under Server Settings, click Mappings
  3. Add Logical Path as "/rest" 
  4. Add Directory Path as the actual path of your files, "E:/website/rest"
  5. Under Data & Services, click REST services
  6. Add the Logical Path you used above, "/rest"
  7. Leave Host as blank
  8. You can leave service mapping blank, it will pick this up from your Application.cfc
  9. Select "Set as default application"
  10. Add service
Step 3: Go to AWS SNS and subscribe your endpoint
  1. Your REST end point will be https://www.mywebsite.com/rest/sns
  2. "rest" says this is a REST call
  3. "sns" is the name of your cfc file that contains the POST function
  4. Once you subscribe, you can manually confirm your subscription by pulling out the subscribeURL in the message or you can write a function in the body of sns.cfc above. 
References: 
GetHttprequest function
cfqueryparam function
HTTPS SNS Issue with CA
SNS JSON format

Wednesday, September 6, 2017

AWS Java SDK Example

AWS Java SDK Example

Connecting to AWS and printing all volumes



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class java-ec2-example{
    public static void main(String[] args){
        String myRegion = "us-east-1";
        String accessKey = "xxxx";
        String secretKey = "xxxx";
        String sessionTk = "xxxx";
        
        //For programmatic access user
        BasicAWSCredentials awsCreds =  new BasicAWSCredentials (accessKey, secretKey);
        
        //For temporary credential
        BasicSessionCredentials sesCreds = new BasicSessionCredentials (accessKey, secretKey, sessionTk);

        //For other credential (e.g. Instance IAM, Environment Variable, etc) leave this as null 
        AWSStaticCredentialsProvider myCredential = null;

        //For this example, we use session credential
        myCredential = new AWSStaticCredentialsProvider(sesCreds);

        AmazonEC2 ec2 = AmazonEC2ClientBuilder.standard()
                     .withCredentials(myCredential)
                     .withRegion(myRegion)
                     .build();

        List<Volume> volumes = ec2.describeVolumes(new DescribeVolumesRequest()).getVolumes();
        for(int i=0;i<volumes.size();i++){
            System.out.println(volumes.get(i));
        }
    }
}

Lines 9-18 are various means to connect to AWS. If you want to rely on the environment variables, you can just comment out line 18 and leave myCredentials as null.

Line 20 is used to build client connector to EC2. Similiar builders are available for all AWS services.

Line 25 returns all the volumes in your account.

Line 26 loops and prints all the volumes.

Wednesday, August 30, 2017

AWS CLI Sample - Find latest snapshot

AWS CLI Sample

Find the most recent snapshots of a volume and update a tag on it



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$volumeid = "vol-0000000000000000000"
$output = aws ec2 describe-snapshots `
   --owner-id 4444444444444 `
   --filters "Name=volume-id,Values='$volumeid'" `
   --query 'Snapshots[].{ID:SnapShotId,StartTime:StartTime}'
$output2 = $output | out-string | convertfrom-json
$highDate = "0"
$highID = "0"
foreach($item in $output2){
   if($item.StartTime -gt $highDate){
      $highDate = $item.StartTime
      $highID = $item.ID
   }
}
write-host $highID
$tag_value = "New Stamp (" + $(get-date).GetDateTimeFormats()[93] + ")"
aws ec2 create-tags --resources $highID --tags "Key='Bookmark',Value='$tag_value'"

Lines 2-5 is your standard describe snapshot command. Be sure to include the account number (owner-id) when we're filtering. The query is to limit the data returned since we don't need to know everything about each snapshots.

Lines 9-14 is looping through all the snapshots returned to get the newest one.

Line 16 is just to put a date stamp. GetDateTimeFormats()[93] returns "yyyy-MM-dd hh:mm:ss" date format.


Sunday, August 27, 2017

AWS CloudFormation Sample


AWS CloudFormation - Using Parameters


 Parameters are input arguments to CloudFormation script. Items like InterfaceID and InstanceProfile are custom parameters where we define the possible values. NameTag is a customer parameter where input values are open-ended with character limit/type limits. The remainder are AWS Supported Parameter Types, more can be found here.

"Parameters":{
        "ImageId":{
            "Description":"Image ID",
            "Type":"String",
            "Default":"ami-284f1a3e",
            "AllowedValues":[
              "ami-284f1a3e",
              "ami-36174e4d"
            ],
            "ConstraintDescription":""

        },
        "InstanceType":{
            "Description":"Instance Type",
            "Type":"String",
            "Default":"t2.small",
            "AllowedValues":[
       "t2.nano",
       "t2.micro",
       "t2.small",
       "t2.medium",
       "t2.large",
       "t2.xlarge",
       "t2.2xlarge"
            ],
            "ConstraintDescription":""
        },
        "KeyName":{
            "Description":"Key Name",
            "Type":"AWS::EC2::KeyPair::KeyName",
            "ConstraintDescription":""
        },
        "InterfaceID":{
            "Description":"Network Interface ID",
            "Type":"String",
            "Default":"eni-db0e3c7a",
            "AllowedValues":[
              "eni-9158e145",
              "eni-db0e3c7a"
            ],
            "ConstraintDescription":""
        },
        "NameTag":{
            "Description":"Name Tag",
            "Type":"String",
            "Default":"NameTag",
            "MinLength":"1",
            "MaxLength":"10",
            "AllowedPattern":"[a-zA-Z][a-zA-Z0-9]*",
            "ConstraintDescription":""
        },
        "InstanceProfile":{
            "Description":"Instance Profile",
            "Type":"String",
            "Default":"read_only_instance",
            "AllowedValues":[
              "read_only_instance",
              "admin_instance"
            ],
            "ConstraintDescription":""
        }
    }

In the Resources, we reference the above parameters using keyword, "Ref"


"Resources": {
        "MyInstance": {
            "Type": "AWS::EC2::Instance",
            "Properties": {
                "ImageId": {
                    "Ref": "ImageId"
                },
                "InstanceType": {
                    "Ref": "InstanceType"
                },
                "KeyName": {
                    "Ref": "KeyName"
                },
                "NetworkInterfaces": [
                    {
                        "NetworkInterfaceId": 
                            {
                            "Ref": "InterfaceID"
                            },
                        "DeviceIndex":"0"
                    }
                ],
                "Tags": [
                    {
                        "Key": "Name",
                        "Value": {
                            "Ref": "NameTag"
                        }
                    }
                ]
            },
            "Metadata": {
                "AWS::CloudFormation::Designer": {
                    "id": "4b94664e-1e18-400c-9733-ff095ff6e854"
                }
            }
        }
    }


The whole thing looks like this:


{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Parameters":{
        "ImageId":{
            "Description":"Image ID",
            "Type":"String",
            "Default":"ami-284f1a3e",
            "AllowedValues":[
              "ami-284f1a3e",
              "ami-36174e4d"
            ],
            "ConstraintDescription":""

        },
        "InstanceType":{
            "Description":"Instance Type",
            "Type":"String",
            "Default":"t2.small",
            "AllowedValues":[
       "t2.nano",
       "t2.micro",
       "t2.small",
       "t2.medium",
       "t2.large",
       "t2.xlarge",
       "t2.2xlarge"
            ],
            "ConstraintDescription":""
        },
        "KeyName":{
            "Description":"Key Name",
            "Type":"AWS::EC2::KeyPair::KeyName",
            "ConstraintDescription":""
        },
        "InterfaceID":{
            "Description":"Network Interface ID",
            "Type":"String",
            "Default":"eni-db0e3c7a",
            "AllowedValues":[
              "eni-9158e145",
              "eni-db0e3c7a"
            ],
            "ConstraintDescription":""
        },
        "NameTag":{
            "Description":"Name Tag",
            "Type":"String",
            "Default":"NameTag",
            "MinLength":"1",
            "MaxLength":"10",
            "AllowedPattern":"[a-zA-Z][a-zA-Z0-9]*",
            "ConstraintDescription":""
        },
        "InstanceProfile":{
            "Description":"Instance Profile",
            "Type":"String",
            "Default":"read_only_instance",
            "AllowedValues":[
              "read_only_instance",
              "admin_instance"
            ],
            "ConstraintDescription":""
        }
    },
    "Metadata": {
        "AWS::CloudFormation::Designer": {
            "4b94664e-1e18-400c-9733-ff095ff6e854": {
                "size": {
                    "width": 60,
                    "height": 60
                },
                "position": {
                    "x": 304,
                    "y": 225
                },
                "z": 0
            }
        }
    },
    "Resources": {
        "MyInstance": {
            "Type": "AWS::EC2::Instance",
            "Properties": {
                "ImageId": {
                    "Ref": "ImageId"
                },
                "InstanceType": {
                    "Ref": "InstanceType"
                },
                "KeyName": {
                    "Ref": "KeyName"
                },
                "NetworkInterfaces": [
                    {
                        "NetworkInterfaceId": 
                            {
                            "Ref": "InterfaceID"
                            },
                        "DeviceIndex":"0"
                    }
                ],
                "Tags": [
                    {
                        "Key": "Name",
                        "Value": {
                            "Ref": "NameTag"
                        }
                    }
                ]
            },
            "Metadata": {
                "AWS::CloudFormation::Designer": {
                    "id": "4b94664e-1e18-400c-9733-ff095ff6e854"
                }
            }
        }
    }
}

When you create stack from this:


Thursday, August 10, 2017

Powershell Network Tools

Few Powershell Network tools

This is when you are in an environment that won't let you install software. I've collected these from various sources. I use them often enough to keep it handy here.

Check for TCP Listener:

1
2
3
4
$socket = new-object net.sockets.tcpclient
$socket.connect("www.google.com",443)
$socket.connected
$socket = $null

Check for TCP Connections on your machine:

This is more than you really need, but it'll print a table of established connections every 5 seconds. If you just want the raw data and all the available connection state, just run lines 2 and 3.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
do{
 $tcpproperties=[System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties()
 $connections=$tcpproperties.GetActiveTcpConnections()
 $output=@()
 foreach($conn in $connections){
   if($conn.State -eq 'Established'){
     $outputObj = New-Object -typename PSObject
     $outputObj | Add-Member -MemberType NoteProperty -Name "LocalAddress" -Value $conn.LocalEndPoint.Address 
     $outputObj | Add-Member -MemberType NoteProperty -Name "LocalPort" -Value $conn.LocalEndPoint.Port
     $outputObj | Add-Member -MemberType NoteProperty -Name "RemoteAddress" -Value $conn.RemoteEndPoint.Address
     $outputObj | Add-Member -MemberType NoteProperty -Name "RemotePort" -Value $conn.RemoteEndPoint.Port
     $outputObj | Add-Member -MemberType NoteProperty -Name "State" -Value $conn.State
     $output=$output + $outputObj
   }
 }
 $output | format-table
 sleep 5
}while($true)


Check for UDP Listener

For this one, I'm also illustrating a listener too. Good for troubleshooting firewall.

Receiver

Run this portion on your receive end and wait for traffic.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$udpObject = new-object system.net.sockets.udpclient(1433)
$udpObject.client.ReceiveTimeout = 10000
do{
  $remoteEndPoint = new-object System.Net.IPEndPoint([System.Net.IPAddress]::Any,0)
  $receiveBytes = $udpObject.Receive([ref]$remoteEndPoint)
  $a = New-Object System.Text.AsciiEncoding
  [String]$returnData = $a.GetString($receiveBytes)
  Write-host "Received: $($returnData.ToString())"
  Write-host "Sent from: $($remoteEndPoint.Address.ToString()) on their port: $($remoteEndPoint.port.ToString())"
  $ans = read-host "More (y/n)?"
}while($ans -ne 'n')
$udpObject.close()

Sender

Then run this from your remote machine to see the information show up on the remote end.
1
2
3
4
5
6
$udpObject = new-object system.net.sockets.udpclient(1433)
$udpObject.connect("10.10.10.1",1433)
$a = new-object System.Text.AsciiEncoding
$byte = $a.GetBytes("$(get-date)")
[void]$udpObject.send($byte,$byte.length)
$udpObject.close()


Check for UDP Connection


1
2
$udpProperties = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties()
$udpProperties.GetActiveUDPListners()


Remote PowerShell Session

Again, more than you need, but incase you don't want to have to type in your password every time. 

1
2
3
4
5
6
$servername = "mydesktop1"
$username = "domain\username"
$password = "password"
$secPassword = convertTo-SecureString $password -AsPlainText -Force
$mycred = New-Object System.Management.Automation.PSCredential($username,$secPassword)
enter-pssession -Credential $mycred -Computername $servername


Send Email

Good way to test your SMTP Server.

1
2
3
4
5
6
$SMTP = "mail.mycompany.net"
$Subject = "Test from $env:Computername"
$toaddress = "myemail@mycompany.net"
$fromaddress = $env:Computername + "@mycompany.net"
$body = "this message was sent via $SMTP at $(get-date)."
send-mailmessage -smtpserver $SMTP -to $toaddress -from $fromaddress -subject $subject -body $body



Wednesday, August 9, 2017

CFML Javascript Update One to Many relationship tables

CFML/Javascript Update Table Page

You have list of users. Those users can belong to 0 to many roles. I needed a simple way to update this table. This is my first approach.

Here's the basic view of the tables and their relationship.




Here's the header portion of the code with the two Javascript functions and the call to my first database table.
alterRole populates a text box that will be submitted when we get ready to save the changes.
resetButton re-enabled all the pressed Add/Delete buttons so that the user can begin again.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<html>
<title>Edit Roles</title>
<head>
<script type ="text/javascript">
function alterRole(action,roleId){
  document.getElementById(roleId).disabled="disabled";
  if(action == 1){
    var txt = document.getElementById("Add").value;
    txt = txt + ";" + roleId;
    document.getElementById("Add").value = txt;
  }else{
    var txt = document.getElementById("Del").value;
    txt = txt + ";" + roleId;
    document.getElementById("Del").value = txt;
  }
}

function resetButton(){
  var inputs = documents.getElementByTagName("BUTTON");
  for (var i=0; i<inputs.length;i++){
    if (inputs[i].type == 'button'){
      input[i].disabled = false;
    }
  }
  document.getElementById("Add").value = "";
  document.getElementById("Del").value = "";
}
</script>

<cfquery name="users" datasource="MYDB">
  select PID, my_UserID from user_list order by userid
</cfquery>
<cfset user_id = 1>
</head>
<cfheader name = "Expires" value "#Now()#">


Below is the first part of the body. This portion is executed when we press Load (user roles) or Save. If we pressed Load then the page loads the currently selected Roles (and available) for the User we selected. If we press Save, then the page inserts into or remove from the intermediate table the UserIDs and RoleIDs. The Form.Add and Form.Del is populated in the last portion of the code.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<body><form action="edit_roles.cfm" method="post">
<cfif isDefined("form.load") or isDefined("form.save")>
  <cfset thisUserID = #form.user_id#>
  <input name="UserName" type="text" value='<cfoutput>#form.user_id#</cfoutput>' size="10" hidden>
  <cfif isDefined("form.save")>
    <cfif len(#form.Add#) gt 0>
      <cfset this1 = #form.Add#>
      <cfset this1 = right(this1,len(this1)-1)>
      <cfset this2 = this1.split(";")>
      <cfloop array = "#this2#" index = "item">
        <cfquery name = "user_to_role" datasource="MYDB">
          insert into user_to_role(user_pid,roleid) values (#thisUserId#,'#item#')
        </cfquery>
      </cfloop>
    </cfif>
    <cfif len(#form.Del#) gt 0>
      <cfset this1 = #form.Del#>
      <cfset this1 = right(this1,len(this1)-1)>
      <cfset this2 = this1.split(";")>
      <cfloop array = "#this2#" index = "item">
        <cfquery name ="user_to_role" datasource="MYDB">
          delete from user_to_role where user_pid= #thisUserId# and roleid = '#item#'
        </cfquery>
      </cfloop>
    </cfif>    
<cfelse>
  <cfset thisUserId = 0>
  <input name ="UserName" type ="text" value ="" size ="10" hidden>
</cfif>


This section populates the entire list of Users and pre-selects the user if we're coming from Save action. This section also has the query that contains the entire list of Roles available and Roles that are already defined for the user.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<h2>User Roles Management</h2>
<h3>Users:
<select name ="user_id" id ="user_id">
  <cfoutput query="users">
    <cfif #PID# eq #thisuserid#>
      <option value="#PID#" selected>#my_USERID#</option>
    <cfelse>
      <option value="#PID#">#my_USERID#</option>
    </cfif>
  </cfoutput>
</selected>
<input name ="Load" type ="Submit" value ="Load"/>
<input name ="Save" type ="Submit" value ="Save"/>
</h3><br>
<cfif #thisuserid# gt 0>
  <cfquery name="user_to_roles" datasource ="MYDB">
    select user_pid, rolename, roles.roleId, ownerid 
    from roles left outer join user_to_role 
    on roles.roleid = user_to_role.roleid 
    and user_pid = #thisuserid# where roletype = 'User' order by rolename
</cfquery>


This section populates a table with the available Roles. If the selected User is already associated with a role then we provide the option to remove, otherwise we have the option add. When we add or delete a role from the selected User, then the RoleID is added to the appropriate text box that will be submitted when we press Save.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<table id ="roles_table">
  <thead>
    <tr>
      <th>RoleName</th>
      <th>Owner</th>
      <th><button type="button" onClick="javascript:resetbutton();">Reset Buttons</button></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <cfoutput query = "user_to_roles">
    <tr>
      <th>#RoleName#</th>
      <th>#Ownerid#</th>
      <cfif len(#user_pid#)>
        <button type="button" onClick="javascript:alterRole(0,'#roleid#');" value ="#roleid#" id = "#roleid#" name = "del_btn">
        Remove</button>
      <cfelse>
        <button type="button" onClick="javascript:alterRole(1,'#roleid#');" value ="#roleid#" id = "#roleid#" name = "add_btn">
        Add</button>
      </cfif>
      <th></th>
      <th></th>
    </tr>
    </cfoutput>
  </tbody>
  </table>
</cfif>
<input name ="Add" id ="Add" type ="text" value ="" size ="100" hidden>
<input name ="Del" id ="Del" type ="text" value ="" size ="100" hidden>
</form>
</body>
</html>

For next version, I plan on using AJAX on every Add/Delete action so that we don't have to submit the entire page. Also, some IF branches can be slimmed down if we move some common actions into a function in CFC.





---

AWS WAF log4j query

How to query AWS WAF log for log4j attacks 1. Setup your Athena table using this instruction https://docs.aws.amazon.com/athena/latest/ug/wa...