How to use a Websocket client to open a long-lived connection to a URL, using PowerShell V2? - json

I am trying to use PowerShell V2 (testing purposes) to initiate a real-time messaging (rtm) instance with Slack. But according to Slack's FAQ, to connect to their rtm API, I need to use the wss:// protocol via a Websocket client to stream events associated. I am also trying to make it an asynchronous connection (receiving as well as connecting).
This doesn't work :
$webSock = New-Object System.Net.WebSocket.ClientWebSocket
$client = New-Object System.Threading.CancellationToken
One other thing is that I need a function to convert from JSON in PowerShell V2.
I tried using this but it doesn't work too:
function ConvertFrom-Json20([object] $item){
add-type -assembly system.web.extensions
$ps_js=new-object system.web.script.serialization.javascriptSerializer
#The comma operator is the array construction operator in PowerShell
return ,$ps_js.DeserializeObject($item)
}

Oddly enough, I had this same need recently. Thanks to Mark Wragg, and his helpful link, here is a quick bit of code to get this going. You'll need at least Windows 8 and Server 2012 to make these things work.
Try{
Do{
$URL = 'ws://YOUR_URL_HERE/API/WebSocketHandler.ashx'
$WS = New-Object System.Net.WebSockets.ClientWebSocket
$CT = New-Object System.Threading.CancellationToken
$WS.Options.UseDefaultCredentials = $true
#Get connected
$Conn = $WS.ConnectAsync($URL, $CT)
While (!$Conn.IsCompleted) {
Start-Sleep -Milliseconds 100
}
Write-Host "Connected to $($URL)"
$Size = 1024
$Array = [byte[]] #(,0) * $Size
#Send Starting Request
$Command = [System.Text.Encoding]::UTF8.GetBytes("ACTION=Command")
$Send = New-Object System.ArraySegment[byte] -ArgumentList #(,$Command)
$Conn = $WS.SendAsync($Send, [System.Net.WebSockets.WebSocketMessageType]::Text, $true, $CT)
While (!$Conn.IsCompleted) {
#Write-Host "Sleeping for 100 ms"
Start-Sleep -Milliseconds 100
}
Write-Host "Finished Sending Request"
#Start reading the received items
While ($WS.State -eq 'Open') {
$Recv = New-Object System.ArraySegment[byte] -ArgumentList #(,$Array)
$Conn = $WS.ReceiveAsync($Recv, $CT)
While (!$Conn.IsCompleted) {
#Write-Host "Sleeping for 100 ms"
Start-Sleep -Milliseconds 100
}
#Write-Host "Finished Receiving Request"
Write-Host [System.Text.Encoding]::utf8.GetString($Recv.array)
}
} Until ($WS.State -ne 'Open')
}Finally{
If ($WS) {
Write-Host "Closing websocket"
$WS.Dispose()
}
}

It is possible to use .NET's System.Net.WebSockets.ClientWebSocket class to do this. You need to be running Windows 8 or Server 2012 or newer as your underlying OS to utilise this class, so therefore I think you'd have at least PowerShell v3 regardless (and as a result the ConvertFrom-Json cmdlet). You also need to make use of the System.ArraySegment .NET class.
I've created a simple framework that demonstrates how to use the various classes to interact with the Slack RTM API from a PowerShell script. You can find the project on GitHub here:
https://github.com/markwragg/Powershell-SlackBot
I've also blogged about it in more detail here: http://wragg.io/powershell-slack-bot-using-the-real-time-messaging-api/

Related

New-AzureADApplication RequiredResourceAccess

I am new to Powershell so bear with me if I do not use the correct terminology.
I am trying to use a snippet of code I found in order to register an application and set -RequiredResourceAccess. Everything I find in regarding this approaches it in the same basic way so I wonder if this is an issue with 7 vs 5 as I have run in to. I just don't know enough.
With the following code I get the below error as if it's seeing a string instead of an object. Thought this how I have seen it done elsewhere.
Set-AzureADApplication: Cannot bind parameter 'RequiredResourceAccess'. Cannot convert value "class RequiredResourceAccess {
ResourceAppId: 00000003-0000-0000-c000-000000000000
ResourceAccess: System.Collections.Generic.List1[Microsoft.Open.AzureAD.Model.ResourceAccess] } " to type "Microsoft.Open.AzureAD.Model.RequiredResourceAccess". Error: "Cannot convert the "class RequiredResourceAccess { ResourceAppId: 00000003-0000-0000-c000-000000000000 ResourceAccess: System.Collections.Generic.List1[Microsoft.Open.AzureAD.Model.ResourceAccess]
}
" value of type "Deserialized.Microsoft.Open.AzureAD.Model.RequiredResourceAccess" to type "Microsoft.Open.AzureAD.Model.RequiredResourceAccess"."
$acc1 = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" -ArgumentList "62a82d76-70ea-41e2-9197-370581804d09","Role" # Group.ReadWrite.All
$acc2 = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" -ArgumentList "df021288-bdef-4463-88db-98f22de89214","Role" # User.Read.All
$req.ResourceAccess = $acc1,$acc2
$req.ResourceAppId = "00000003-0000-0000-c000-000000000000" # MS Graph Resource ID
$ccredsApp = New-AzureADApplication `
-IdentifierUris ("app://{0}" -f $settings.webAPI.name) `
-DisplayName ("{0}-clientcreds" -f $settings.webAPI.name) `
-Oauth2AllowImplicitFlow $false
Set-AzureADApplication -ObjectId $ccredsApp.objectId -RequiredResourceAccess $req`

Use a parameter switch to change how a function behaves

My main PowerShell code runs a function that logs to the Windows eventlog. If the level is error it uses a separate event ID which then our monitoring will pick up that exact ID and run an action. However, if I want to specify in the parameter of the main script (not the function) that this time running it use a different Event ID so it will NOT action monitoring, I don't know where to even start on that.
Is there a way to provide a switch parameter in the main script like $NoAlert which then changes the Event ID in the function?
The function of logging lives in a PowerShell module I created. I am importing the module at the beginning of the script and then calling the function during the main script body.
Here is the function:
function WriteLog-SRTProd {
Param(
[string]$logT,
[Parameter(Mandatory=$true)][string]$level,
[String]$LogFileDirT = "\\ServerA\Logs"
)
$RSLogfileT = (Get-ChildItem -Path $LogFileDirT |
sort LastWriteTime |
select -Last 1).Name
## make sure a level is correctly selected (mandatory)
if ("Error","Info","Warn" -NotContains $Level) {
throw "$($Environment) is not a valid name! Please use 'Error', 'Warn', or 'Info'"
}
if ($Level -eq "Info") {
Add-Content -Path "$LogFileDirT\$RSLogFileT" -Value "$(Get-Date -format MM-dd-yyyy::HH:mm:ss) INFO $logT"
Write-EventLog -LogName Application -Source TEST_MAINT -EntryType Information -EventId 100 -Message $logT -Category 0
}
if ($Level -eq "Warn") {
Add-Content -Path "$LogFileDirT\$RSLogFileT" -Value "$(Get-Date -format MM-dd-yyyy::HH:mm:ss) WARN $logT"
Write-EventLog -LogName Application -Source TEST_MAINT -EntryType Warning -EventId 200 -Message $logT -Category 0
}
if ($Level -eq "Error") {
Add-Content -Path "$LogFileDirT\$RSLogFileT" -Value "$(Get-Date -format MM-dd-yyyy::HH:mm:ss) ERROR $logT"
Write-EventLog -LogName Application -Source TEST_MAINT -EntryType Error -EventId 300 -Message $logT -Category 0
}
}
I'd like to run my script like this. When the $NoAlert is passed, it will send that switch to the function. Is this possible? Can I just add the switch in both places and use an if statement in the function for when the NoAlert switch is used?
PS C:\> .\Maintenance.ps1 -NoAlert
Param(
[switch]$NoAlert
)
WriteLog-SRTProd -level Error -logT "Custom Error Message"
I have created own function for logging and stored/installed as module, below is the part of my log module :
you can customize the write statements and add your code for event log. I have added 'NoAction' enum member as per your requirements.
I have used one Enum to separate the log levels
Enum Severity
{
Error = 3
Warning = 4
Informational = 6
Debug = 7
Verbose = 8
NoAction = 0 # AS PER YOUR REQUIREMENTS
}
function Write-Log()
{
[cmdletbinding()]
param
(
[Parameter(Position=0,mandatory=$true)]
[Severity] $LogLevel,
[Parameter(Position=1,mandatory=$true)]
[String] $Message
)
$TimeStamp = "$(Get-Date -format HH:mm:ss)" ;
Switch($LogLevel)
{
([Severity]::Error.ToString())
{
Write-Error "`t$TimeStamp : $Message`n" -ErrorAction Stop
break;
}
([Severity]::Warning.ToString())
{
Write-Warning "`t$TimeStamp : $Message`n" -WarningAction Continue
break;
}
([Severity]::Informational.ToString())
{
Write-Information "INROMATION:`t$TimeStamp : $Message`n" -InformationAction Continue
break;
}
([Severity]::Verbose.ToString())
{
Write-Verbose "`t$TimeStamp : $Message`n"
break;
}
([Severity]::NoAction.ToString())
{
Write-Verbose "`t$TimeStamp : $Message`n"
break;
}
} # END OF SWITCH
} # END OF FUNCTION
Sample Call :
Write-Log -LogLevel ([Severity]::Informational) -Message "test log message using info level"
Output :
INROMATION: 09:40:15 : test log message using info level
I have decided to just add a new parameter to both function and main script named $NoAlert. I have added an If($NoAlert){WriteLog-SRPProd -NoAlert} to the main script (messy, but its what I needed done). then in the Function, If($NoAlert){EventID 111}. so basically I am using the switch in the main script that then calls the NoAlert switch in the function. This is all done with a few added If/Else statements.
Hopefully that makes sense. Like I said its not the best answer, but I wanted to get it done and still provide an answer here in this post.

Powershell - mySQL Query, error every other run

I am working on a powershell script that needs some input from a mySQL database. For the life of me I can't tell what I've done wrong here.
Every other time I run this script, I get an error Exception calling "Open" with "0" argument(s): "Out of sync with server"[0]. So, the first run, it will pull the expected data and dump it on my screen, then on the next run I get that error. And the cycle just repeats. Here is my full code (right now its just a test query to pull then dump the data. If it matters, the mySQL server is running MariaDB 10.3.14 on a Ubuntu 18.04 host.
$error.Clear()
$sqlQuery = get-content -path "C:\querytext.sql" -Raw
$sqlUser = "myuser"
$sqlPass = "mypass"
$sqlHost = "myserver"
$sqlDB = "dbname"
$connectionString = "server= $sqlHost;port=3306;uid=$sqlUser;pwd=$sqlPass;database=$sqlDB"
Try{
$connection = New-Object MySql.data.MySqlClient.MySqlConnection
$connection.ConnectionString = $connectionString
$connection.Open()
$command = New-Object MySql.data.MySqlClient.MySqlCommand($sqlQuery,$connection)
$dataAdapter = New-Object MySql.data.MySqlClient.MySqlDataAdapter($command)
$dataSet = New-Object System.Data.DataSet
$dataAdapter.fill($dataSet, "data") | Out-Null
$command.Dispose()
$sqlResults = $dataSet.tables["data"]
}
Catch {
Write-Host "ERROR : Unable to run query : $query `n$Error[0]"
}
$connection.close()
$sqlResults | Format-Table
$sqlResults | ForEach-Object {
write-host $_.fname
}
Might I suggest using the SQL PS module:
https://learn.microsoft.com/en-us/sql/powershell/download-sql-server-ps-module?view=sql-server-2017
That page has installation instructions and its from Microsoft. Personally, the dotnet class you are using, it works, but its relatively difficult to work with.
Connecting to a DB is much simpler with this module and you do not have to worry about micromanaging connections.
Invoke-Sqlcmd -ServerInstance $sqlHost -Query $sqlQuery -Database $sqlDB -Username $sqlUser -Password $sqlPass
This will return a PS object like every other PS cmdlet.

How to correctly pass variables & source version to API 2.0 VNext Build in TFS 2015

I'm having difficulty finding how the correct way to pass defined variables and build definition arguments to the new API 2.0 build engine with TFS 2015. I'm using TFS 2015 Update 3 on-premise .
I've triggered a POST with powershell that looks like this:
$Build_Definition_ID = 1234
$TFSInstanceURL = 'http://tfsservername:port/tfs'
$ProjectCollection = 'CollectionName'
$TeamProject = 'ProjectName'
$Changeset = "12345"
$UserName = "$env:USERDOMAIN\$env:USERNAME"
$UserNamePartial = $env:USERNAME
$body = #"
{
"definition": {
"id": "$Build_Definition_ID"
}
}
"#
$baseUri = $TFSInstanceURL+"/"+$ProjectCollection+"/"+$TeamProject+"/_apis/build"
$postUri = $baseUri+"/builds?api-version=2.0"
##Create a new PSCredential based on username/password
$User = 'foo\bar'
$Password = 'examplepass'
$securePassword = $Password | ConvertTo-SecureString -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($User, $securePassword)
### Queue a build ###
##Call the REST API for TFS that does a POST request to queue a build with the body of the request to be the build definition
$buildResponse = Invoke-RestMethod -Method POST -Credential $credential -ContentType application/json -Uri $postUri -Body $body
#Write-Host (ConvertTo-Json $buildResponse)
#ConvertTo-Json $buildResponse | out-file -FilePath $Changeset-ResponseJson.json -Force
The powershell script is successfully launching the definition. However, I'm still not successfully:
- Passing in the specific source version I want to run against (example C12345)
- Passing in the custom variable values
Additionally:
If you know of the proper way to pass in the arguments such as the folder to map from source (to allow dynamically choosing different branches) then this would help.
Current resources I've evaluated:
Visual Studio Docs > Api > Build > Builds
Postman - GET - Definition Details - Reviewed response for possible correct structure to submit
The body part for the REST API should look like:
{
"definition": {
"id": 28
},
"sourceBranch": "$/xxxx/xxxx",
"SourceVersion": "Cxxxx",
}
Then you can specify the sourceBranch and SourceVersion.
===================================================================
An example:
$Build_Definition_ID = '28'
$TFSInstanceURL = 'http://tfsservername:port/tfs'
$ProjectCollection = 'DefaultCollection'
$TeamProject = 'TestCase'
$Changeset = "C139"
$sourceBranch = "$/TestCase/TestCaseProject-branch"
$body = #"
{
"definition": {
"id": "$Build_Definition_ID"
},
"sourceBranch": "$sourceBranch",
"SourceVersion": "$Changeset",
}
"#

How do I grab the contact for a user in my Google Apps domain?

Here's the setup code (I'm using Powershell since it's usually convenient)
$a1= Add-Type -Path "D:\Google2.1\Google.GData.Client.dll" -passthru
$a2= Add-Type -Path "D:\Google2.1\Google.GData.Apps.dll" -passthru
$a3= Add-Type -Path "D:\Google2.1\Google.GData.Contacts.dll" -passthru
$a4= Add-Type -Path "D:\Google2.1\Google.GData.Extensions.dll" -passthru
$reqSet = New-Object Google.GData.Client.RequestSettings("testApp", $config.admin, $config.password)
$reqSet.AutoPaging = $true
$contReq = New-Object Google.Contacts.ContactsRequest($reqSet)
So, now I try to retrieve contacts:
$contReq.GetContacts()
This works... and gives me my contacts (as a domain super admin). Cool
$contReq.GetContacts("arbitraryuser#mydomain.com")
This gives me an error like
format-default : Execution of request failed: https://www.google.com/m8/feeds/contacts/arbitraryuser#mydomain.com/full
I did get a GDataLoggingRequestFactory factor attached to log the requests as well, and just indicated a 401 error, with no details.
Question starts to be old, but since i'm working on a project like this ...
I'm using the latest .NET client library distribution
Here's a sample of PS code that works :
$a1 = Add-Type -Path "C:\Program Files (x86)\Google\Google Data API SDK\Redist\Google.GData.Client.dll" -passthru
$a2 = Add-Type -Path "C:\Program Files (x86)\Google\Google Data API SDK\Redist\Google.GData.Contacts.dll" -passthru
$a3 = Add-Type -Path "C:\Program Files (x86)\Google\Google Data API SDK\Redist\Google.GData.Extensions.dll" -passthru
$Settings = New-Object Google.GData.Client.RequestSettings( "MyApp", "mybelovedtrashbox#gmail.com", "mypassword" )
$reqSet = New-Object Google.Contacts.ContactsRequest( $Settings )
$Contacts = $reqSet.GetContacts()
#loop version
foreach( $Contact in $Contacts.Entries ){
$Contact.PrimaryEmail.Address
}
#selection version
$user = $Contacts.Entries |? { $_.PrimaryEmail.Address -eq "john.doe#gmail.com" }
$user.Title
Hope this helps ...
I'm working on code that would allow to update my gmail contacts from outlook contacts, let me know if you need intails ... :)