├── Source ├── PowerShell.ico ├── PowerShell-Kernel │ ├── powershell.config.json │ ├── Config │ │ └── PowerShellOptions.cs │ ├── Properties │ │ └── launchSettings.json │ ├── kernel.json │ ├── PowerShell-Kernel.Config.json │ ├── SessionHelper.cs │ ├── ExecutionResult.cs │ ├── ScalarHelper.cs │ ├── Kernel.cs │ ├── PowerShell-Kernel.csproj │ ├── Commands │ │ └── WriteJupyterCommand.cs │ └── PowerShellEngine.cs ├── Jupyter │ ├── Messages │ │ ├── Content │ │ │ ├── Content.cs │ │ │ ├── ShutdownContent.cs │ │ │ ├── CompleteRequestContent.cs │ │ │ ├── TargetName.cs │ │ │ ├── ExecuteReplyContent.cs │ │ │ ├── StatusContent.cs │ │ │ ├── StderrContent.cs │ │ │ ├── StdoutContent.cs │ │ │ ├── ExecuteRequestPublishContent.cs │ │ │ ├── ExecuteResultPublishContent.cs │ │ │ ├── ExecuteErrorPublishContent.cs │ │ │ ├── ExecuteResultReplyContent.cs │ │ │ ├── ExecuteErrorReplyContent.cs │ │ │ ├── CompleteReplyContent.cs │ │ │ ├── DisplayDataContent.cs │ │ │ ├── ExecuteRequestContent.cs │ │ │ ├── CommInfoReplyContent.cs │ │ │ ├── KernelInfoReplyContent.cs │ │ │ └── LanguageInfoContent.cs │ │ ├── ExecutionResult.cs │ │ ├── KernelState.cs │ │ ├── CommOpenMessage.cs │ │ ├── CommMessage.cs │ │ ├── Header.cs │ │ ├── Message.cs │ │ └── MessageType.cs │ ├── Server │ │ ├── IReplEngine.cs │ │ ├── IKernelSockets.cs │ │ ├── Handlers │ │ │ ├── IMessageHandler.cs │ │ │ ├── CommOpenHandler.cs │ │ │ ├── CommInfoHandler.cs │ │ │ ├── ShutdownHandler.cs │ │ │ ├── KernelInfoHandler.cs │ │ │ └── ExecuteHandler.cs │ │ ├── IExecutionResult.cs │ │ ├── ConnectionInformation.cs │ │ ├── Heartbeat.cs │ │ ├── JsonHelper.cs │ │ ├── Shell.cs │ │ └── MessageSender.cs │ ├── Config │ │ └── LoggerOptions.cs │ ├── Constants.cs │ ├── Jupyter.csproj │ ├── Session.cs │ └── Validator.cs ├── nuget.config ├── tools │ ├── VERIFICATION.txt │ ├── chocolateybeforemodify.ps1 │ ├── chocolateyuninstall.ps1 │ └── chocolateyinstall.ps1 └── Jupyter-PowerShell.sln ├── .dockerignore ├── binder └── Dockerfile ├── .vscode ├── tasks.json └── launch.json ├── LICENSE.md ├── Dockerfile ├── ReadMe.md ├── .gitattributes ├── jupyter-powershell.nuspec ├── LiterateDevOps.md ├── .gitignore ├── Release.ipynb ├── nteract - plotly.ipynb └── LiterateDevOps.ipynb /Source/PowerShell.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jaykul/Jupyter-PowerShell/HEAD/Source/PowerShell.ico -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints/ 2 | .vs/ 3 | .vscode/ 4 | Output/ 5 | Output - Copy/ 6 | Source/Output/ -------------------------------------------------------------------------------- /Source/PowerShell-Kernel/powershell.config.json: -------------------------------------------------------------------------------- 1 | {"Microsoft.PowerShell:ExecutionPolicy":"RemoteSigned"} 2 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/Content.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | public class Content 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Source/PowerShell-Kernel/Config/PowerShellOptions.cs: -------------------------------------------------------------------------------- 1 | public class PowerShellOptions 2 | { 3 | public bool JsonOutput { get; set; } 4 | } -------------------------------------------------------------------------------- /binder/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jaykul/powershell-notebook:b11860696 as builder 2 | 3 | # Put the current notebooks in there 4 | COPY *.ipynb ${HOME}/ -------------------------------------------------------------------------------- /Source/PowerShell-Kernel/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "PowerShell-Kernel": { 4 | "commandName": "Project" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /Source/Jupyter/Server/IReplEngine.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Server 2 | { 3 | public interface IReplEngine 4 | { 5 | IExecutionResult Execute(string script); 6 | } 7 | } -------------------------------------------------------------------------------- /Source/PowerShell-Kernel/kernel.json: -------------------------------------------------------------------------------- 1 | { 2 | "argv": [ 3 | "PowerShell-Kernel", 4 | "{connection_file}" 5 | ], 6 | "display_name": "PowerShell", 7 | "language": "PowerShell" 8 | } -------------------------------------------------------------------------------- /Source/Jupyter/Config/LoggerOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Config 2 | { 3 | public class LoggerOptions 4 | { 5 | public bool ConsoleOutput { get; set; } 6 | public bool DebuggerOutput { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Source/Jupyter/Server/IKernelSockets.cs: -------------------------------------------------------------------------------- 1 | using NetMQ.Sockets; 2 | 3 | namespace Jupyter.Server 4 | { 5 | public interface IKernelSocketProvider 6 | { 7 | RouterSocket ShellSocket { get; } 8 | PublisherSocket PublishSocket { get; } 9 | } 10 | } -------------------------------------------------------------------------------- /Source/PowerShell-Kernel/PowerShell-Kernel.Config.json: -------------------------------------------------------------------------------- 1 | { 2 | "PowerShell": { 3 | "JsonOutput": false 4 | }, 5 | 6 | "Logger": { 7 | "ConsoleOutput": true, 8 | "DebuggerOutput": false 9 | }, 10 | 11 | "Debug": { 12 | "BreakOnStart": false 13 | } 14 | } -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/ShutdownContent.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using Newtonsoft.Json; 4 | 5 | public class ShutdownContent : Content 6 | { 7 | [JsonProperty("restart")] 8 | public bool Restart { get; set; } 9 | 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Source/Jupyter/Server/Handlers/IMessageHandler.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Server.Handlers 2 | { 3 | using Jupyter.Messages; 4 | using NetMQ.Sockets; 5 | 6 | public interface IMessageHandler 7 | { 8 | void HandleMessage(Message message, RouterSocket serverSocket, PublisherSocket ioPub); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/CompleteRequestContent.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using Newtonsoft.Json; 4 | 5 | public class CompleteRequestContent : Content 6 | { 7 | [JsonProperty("code")] 8 | public string Code { get; set; } 9 | 10 | [JsonProperty("cursor_pos")] 11 | public int CursorPosition { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Source/Jupyter/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Jupyter 6 | { 7 | public class Constants 8 | { 9 | public const string USERNAME = "ipowershell_kernel"; 10 | 11 | public const string PROTOCOL_VERSION = "5.0"; 12 | 13 | public const string DELIMITER = ""; 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/TargetName.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using Newtonsoft.Json; 4 | 5 | public class TargetName : Content 6 | { 7 | public TargetName(string name) 8 | { 9 | Name = name; 10 | } 11 | 12 | [JsonProperty("target_name")] 13 | public string Name { get; set; } = string.Empty; 14 | } 15 | } -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/ExecuteReplyContent.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using Newtonsoft.Json; 4 | 5 | public class ExecuteReplyContent : Content 6 | { 7 | [JsonProperty("status")] 8 | public ExecutionResult Status { get; set; } 9 | 10 | [JsonProperty("execution_count")] 11 | public int ExecutionCount { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "taskName": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/PowerShell-Kernel/PowerShell-Kernel.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /Source/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/StatusContent.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Converters; 5 | 6 | public class StatusContent : Content 7 | { 8 | public StatusContent(KernelState state) 9 | { 10 | ExecutionState = state; 11 | } 12 | 13 | [JsonProperty("execution_state")] 14 | public KernelState ExecutionState { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/StderrContent.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using Newtonsoft.Json; 4 | 5 | public class StderrContent : Content 6 | { 7 | public StderrContent(string text) 8 | { 9 | Text = text; 10 | } 11 | 12 | [JsonProperty("name")] 13 | public string Name { get; } = "stderr"; 14 | 15 | [JsonProperty("text")] 16 | public string Text { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/StdoutContent.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using Newtonsoft.Json; 4 | 5 | public class StdoutContent : Content 6 | { 7 | public StdoutContent(string text) 8 | { 9 | Text = text; 10 | } 11 | 12 | [JsonProperty("name")] 13 | public string Name { get; } = "stdout"; 14 | 15 | [JsonProperty("text")] 16 | public string Text { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Source/tools/VERIFICATION.txt: -------------------------------------------------------------------------------- 1 | VERIFICATION 2 | Verification is intended to assist the Chocolatey moderators and community 3 | in verifying that this package's contents are trustworthy. 4 | 5 | To verify the files, from the root of the package output: 6 | 7 | # Remove the nuget-generated folders and files: 8 | Remove-Item _rels, package, *.* -recurse 9 | 10 | # Check the catalog against all the remaining files: 11 | Test-FileCatalog .\tools\Jupyter-PowerShell.cat . -Detailed -------------------------------------------------------------------------------- /Source/Jupyter/Messages/ExecutionResult.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Converters; 5 | using System.Runtime.Serialization; 6 | 7 | [JsonConverter(typeof(StringEnumConverter))] 8 | public enum ExecutionResult 9 | { 10 | [EnumMember(Value = "ok")] 11 | Ok, 12 | 13 | [EnumMember(Value = "error")] 14 | Error, 15 | 16 | [EnumMember(Value = "abort")] 17 | Abort, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/KernelState.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Converters; 5 | using System.Runtime.Serialization; 6 | 7 | [JsonConverter(typeof(StringEnumConverter))] 8 | public enum KernelState 9 | { 10 | [EnumMember(Value = "busy")] 11 | Busy, 12 | 13 | [EnumMember(Value = "idle")] 14 | Idle, 15 | 16 | [EnumMember(Value = "starting")] 17 | Starting 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/CommOpenMessage.cs: -------------------------------------------------------------------------------- 1 | // namespace Jupyter.Messages 2 | // { 3 | // using System.Linq; 4 | // using System.Collections.Generic; 5 | // using Newtonsoft.Json; 6 | 7 | // public class CommOpenMessage: CommMessage 8 | // { 9 | 10 | // public CommOpenMessage(string name) : base() 11 | // { 12 | // Target = name; 13 | // } 14 | 15 | // [JsonProperty("target_name")] 16 | // public string Target { get; set; } = string.Empty; 17 | // } 18 | // } 19 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/ExecuteRequestPublishContent.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using Newtonsoft.Json; 4 | 5 | public class ExecuteRequestPublishContent : Content 6 | { 7 | public ExecuteRequestPublishContent(string code, int executionCount) 8 | { 9 | Code = code; 10 | ExecutionCount = executionCount; 11 | } 12 | 13 | [JsonProperty("code")] 14 | public string Code { get; set; } 15 | 16 | [JsonProperty("execution_count")] 17 | public int ExecutionCount { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/ExecuteResultPublishContent.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using System.Collections.Generic; 4 | using Newtonsoft.Json; 5 | 6 | public class ExecuteResultPublishContent : DisplayDataContent 7 | { 8 | public ExecuteResultPublishContent(DisplayDataContent content, int executionCount = 0) : base(content.Data) 9 | { 10 | MetaData = content.MetaData; 11 | ExecutionCount = executionCount; 12 | } 13 | 14 | [JsonProperty("execution_count")] 15 | public int ExecutionCount { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/ExecuteErrorPublishContent.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using System.Collections.Generic; 4 | using Newtonsoft.Json; 5 | 6 | public class ExecuteErrorPublishContent : Content 7 | { 8 | [JsonProperty("execution_count")] 9 | public int ExecutionCount { get; set; } 10 | 11 | [JsonProperty("ename")] 12 | public string EName { get; set; } 13 | 14 | [JsonProperty("evalue")] 15 | public string EValue { get; set; } 16 | 17 | [JsonProperty("traceback")] 18 | public List Traceback { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/ExecuteResultReplyContent.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using System.Collections.Generic; 4 | using Newtonsoft.Json; 5 | 6 | public class ExecuteResultReplyContent : ExecuteReplyContent 7 | { 8 | public ExecuteResultReplyContent() 9 | { 10 | this.Status = ExecutionResult.Ok; 11 | } 12 | 13 | [JsonProperty("payload")] 14 | public List> Payload { get; set; } 15 | 16 | [JsonProperty("user_expressions")] 17 | public Dictionary UserExpressions { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Source/Jupyter/Server/IExecutionResult.cs: -------------------------------------------------------------------------------- 1 | using Jupyter.Messages; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Jupyter.Server 6 | { 7 | 8 | public interface IErrorResult 9 | { 10 | string Name { get; set; } 11 | string Message { get; set; } 12 | List StackTrace { get; set; } 13 | 14 | } 15 | 16 | 17 | public interface IExecutionResult 18 | { 19 | List Output { get; } 20 | 21 | List Exceptions { get; } 22 | 23 | IErrorResult Error { get; } 24 | 25 | DisplayDataContent GetDisplayData(); 26 | } 27 | } -------------------------------------------------------------------------------- /Source/tools/chocolateybeforemodify.ps1: -------------------------------------------------------------------------------- 1 | # This runs in 0.9.10+ before upgrade and uninstall. 2 | # Use this file to do things like stop services prior to upgrade or uninstall. 3 | # NOTE: It is an anti-pattern to call chocolateyUninstall.ps1 from here. If you 4 | # need to uninstall an MSI prior to upgrade, put the functionality in this 5 | # file without calling the uninstall script. Make it idempotent in the 6 | # uninstall script so that it doesn't fail when it is already uninstalled. 7 | # NOTE: For upgrades - like the uninstall script, this script always runs from 8 | # the currently installed version, not from the new upgraded package version. 9 | 10 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/CommMessage.cs: -------------------------------------------------------------------------------- 1 | // namespace Jupyter.Messages 2 | // { 3 | // using System.Linq; 4 | // using System.Collections.Generic; 5 | // using Newtonsoft.Json; 6 | 7 | // public class CommMessage : Message 8 | // { 9 | // public CommMessage() 10 | // { 11 | // Data = new Dictionary(); 12 | // } 13 | 14 | // [JsonProperty("data")] 15 | // public Dictionary Data { get; set; } = new Dictionary(); 16 | 17 | // [JsonProperty("comm_id")] 18 | // public string CommId { get; set; } = string.Empty; 19 | // } 20 | // } 21 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/ExecuteErrorReplyContent.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using System.Collections.Generic; 4 | using Newtonsoft.Json; 5 | 6 | public class ExecuteErrorReplyContent : ExecuteReplyContent 7 | { 8 | public ExecuteErrorReplyContent() 9 | { 10 | this.Status = ExecutionResult.Error; 11 | } 12 | 13 | [JsonProperty("ename")] 14 | public string EName { get; set; } 15 | 16 | [JsonProperty("evalue")] 17 | public string EValue { get; set; } 18 | 19 | [JsonProperty("traceback")] 20 | public List Traceback { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/CompleteReplyContent.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using System.Collections.Generic; 4 | using Newtonsoft.Json; 5 | 6 | public class CompleteReplyContent : Content 7 | { 8 | [JsonProperty("matches")] 9 | public List Matches { get; set; } 10 | 11 | [JsonProperty("cursor_start")] 12 | public int CursorStart { get; set; } 13 | 14 | [JsonProperty("cursor_end")] 15 | public int CursorEnd { get; set; } 16 | 17 | [JsonProperty("metadata")] 18 | public Dictionary MetaData { get; set; } 19 | 20 | [JsonProperty("status")] 21 | public string Status { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/DisplayDataContent.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using System.Collections.Generic; 4 | using Newtonsoft.Json; 5 | 6 | public class DisplayDataContent : Content 7 | { 8 | public DisplayDataContent(Dictionary data) 9 | { 10 | Data = data; 11 | } 12 | 13 | [JsonProperty("data")] 14 | public Dictionary Data { get; set; } 15 | 16 | [JsonProperty("metadata")] 17 | public Dictionary MetaData { get; set; } = new Dictionary(); 18 | 19 | [JsonProperty("transient")] 20 | public Dictionary Transient { get; set; } = new Dictionary(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/ExecuteRequestContent.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using System.Collections.Generic; 4 | using Newtonsoft.Json; 5 | 6 | public class ExecuteRequestContent : Content 7 | { 8 | [JsonProperty("code")] 9 | public string Code { get; set; } 10 | 11 | [JsonProperty("silent")] 12 | public bool Silent { get; set; } 13 | 14 | [JsonProperty("store_history")] 15 | public bool StoreHistory { get; set; } 16 | 17 | [JsonProperty("user_expressions")] 18 | public Dictionary UserExpressions { get; set; } 19 | 20 | [JsonProperty("user_variables")] 21 | public List UserVariables { get; set; } 22 | 23 | [JsonProperty("allow_stdin")] 24 | public bool AllowStdin { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/CommInfoReplyContent.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using System.Linq; 4 | using System.Collections.Generic; 5 | using Newtonsoft.Json; 6 | 7 | public class CommInfoReplyContent : Content 8 | { 9 | 10 | public CommInfoReplyContent() 11 | { 12 | Comms = new Dictionary(); 13 | } 14 | 15 | public CommInfoReplyContent(Dictionary comms) 16 | { 17 | Comms = comms; 18 | } 19 | 20 | public CommInfoReplyContent(Dictionary comms) 21 | { 22 | Comms = comms.ToDictionary(kvp => kvp.Key, kvp => new TargetName(kvp.Value)); 23 | } 24 | 25 | [JsonProperty("comms")] 26 | public Dictionary Comms { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/KernelInfoReplyContent.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using Newtonsoft.Json; 4 | using System.Collections.Generic; 5 | 6 | public class KernelInfoReplyContent : Content 7 | { 8 | [JsonProperty("protocol_version")] 9 | public string ProtocolVersion { get; set; } 10 | 11 | [JsonProperty("implementation")] 12 | public string Implementation { get; set; } 13 | 14 | [JsonProperty("implementation_version")] 15 | public string ImplementationVersion { get; set; } 16 | 17 | [JsonProperty("language_info")] 18 | public LanguageInfoContent LanguageInfo { get; set; } 19 | 20 | [JsonProperty("banner")] 21 | public string Banner { get; set; } 22 | 23 | [JsonProperty("help_links")] 24 | public List> HelpLinks { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Source/Jupyter/Server/Handlers/CommOpenHandler.cs: -------------------------------------------------------------------------------- 1 | // namespace Jupyter.Server.Handlers 2 | // { 3 | // using Microsoft.Extensions.Logging; 4 | // using Jupyter.Messages; 5 | // using NetMQ.Sockets; 6 | // using Newtonsoft.Json; 7 | 8 | // public class CommOpenHandler : IMessageHandler 9 | // { 10 | // private readonly ILogger _logger; 11 | 12 | 13 | // public CommOpenHandler(ILogger logger) 14 | // { 15 | // _logger = logger; 16 | // } 17 | 18 | // public void HandleMessage(Message message, RouterSocket serverSocket, PublisherSocket ioPub) 19 | // { 20 | // TargetName targetName = message.Content as TargetName; 21 | // Message replyMessage = new Message(MessageType.CommClose, message.Header); 22 | 23 | // _logger.LogInformation("Sending comm_info_reply"); 24 | // serverSocket.SendMessage(replyMessage); 25 | // } 26 | 27 | // } 28 | // } -------------------------------------------------------------------------------- /Source/PowerShell-Kernel/SessionHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Management.Automation; 5 | using System.Management.Automation.Runspaces; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Jupyter.PowerShell 11 | { 12 | public static class SessionHelper 13 | { 14 | public static void LoadCmdlets(this InitialSessionState iss, Assembly core) 15 | { 16 | // Load all the Cmdlets that are in this assembly automatically. 17 | foreach (var t in core.GetTypes()) 18 | { 19 | if (t.GetCustomAttributes(typeof(CmdletAttribute), false) is CmdletAttribute[] cmdlets) 20 | { 21 | foreach (var cmdlet in cmdlets) 22 | { 23 | iss.Commands.Add(new SessionStateCmdletEntry($"{cmdlet.VerbName}-{cmdlet.NounName}", t, $"{t.Name}.xml")); 24 | } 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Header.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using Newtonsoft.Json; 4 | 5 | /// 6 | /// The Jupyter Message Header 7 | /// 8 | public class Header 9 | { 10 | [JsonProperty("msg_id")] 11 | public string MessageId { get; set; } = System.Guid.NewGuid().ToString("N"); 12 | 13 | [JsonProperty("username")] 14 | public string Username { get; set; } = Constants.USERNAME; 15 | 16 | [JsonProperty("session")] 17 | public string Session { get; set; } 18 | 19 | [JsonProperty("date")] 20 | public string Date { get; set; } = System.DateTimeOffset.UtcNow.ToString("o"); 21 | 22 | [JsonProperty("msg_type")] 23 | public MessageType MessageType { get; set; } 24 | 25 | [JsonProperty("version")] 26 | public string Version { get; set; } = Constants.PROTOCOL_VERSION; 27 | 28 | public Header(MessageType messageType, string session) 29 | { 30 | Session = session; 31 | MessageType = messageType; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2017 Joel Bennett 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 4 | 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 8 | 9 | ---- 10 | 11 | Some portions: 12 | 13 | Copyright 2014 Zohaib Rauf 14 | 15 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 16 | 17 | http://www.apache.org/licenses/LICENSE-2.0 18 | 19 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 20 | -------------------------------------------------------------------------------- /Source/Jupyter/Server/Handlers/CommInfoHandler.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Server.Handlers 2 | { 3 | using Microsoft.Extensions.Logging; 4 | using Jupyter.Messages; 5 | using NetMQ.Sockets; 6 | using Newtonsoft.Json; 7 | 8 | public class CommInfoHandler : IMessageHandler 9 | { 10 | private readonly ILogger _logger; 11 | 12 | 13 | public CommInfoHandler(ILogger logger) 14 | { 15 | _logger = logger; 16 | } 17 | 18 | public void HandleMessage(Message message, RouterSocket serverSocket, PublisherSocket ioPub) 19 | { 20 | TargetName targetName = message.Content as TargetName; 21 | Message replyMessage = new Message(MessageType.CommInfoReply, CreateCommInfoReply(targetName), message.Header); 22 | 23 | _logger.LogInformation("Sending comm_info_reply"); 24 | serverSocket.SendMessage(replyMessage); 25 | } 26 | 27 | private CommInfoReplyContent CreateCommInfoReply(TargetName name) 28 | { 29 | CommInfoReplyContent CommInfoReply = new CommInfoReplyContent( new System.Collections.Generic.Dictionary { }) 30 | { 31 | 32 | }; 33 | 34 | return CommInfoReply; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Source/Jupyter/Server/Handlers/ShutdownHandler.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Server.Handlers 2 | { 3 | using Microsoft.Extensions.Logging; 4 | using Jupyter.Messages; 5 | using NetMQ.Sockets; 6 | using Newtonsoft.Json; 7 | 8 | public class ShutdownHandler : IMessageHandler 9 | { 10 | private readonly ILogger _logger; 11 | private readonly Heartbeat _heartbeat; 12 | 13 | private readonly Shell _shell; 14 | 15 | public ShutdownHandler(ILogger logger, Heartbeat heartbeat, Shell shell) 16 | { 17 | _logger = logger; 18 | _heartbeat = heartbeat; 19 | _shell = shell; 20 | } 21 | 22 | public void HandleMessage(Message message, RouterSocket serverSocket, PublisherSocket ioPub) 23 | { 24 | var shutdownRequest = message.Content as ShutdownContent; 25 | 26 | _logger.LogInformation("Stopping heartbeat"); 27 | _heartbeat.Stop(); 28 | 29 | Message replyMessage = new Message(MessageType.ShutDownReply, shutdownRequest, message.Header); 30 | 31 | _logger.LogInformation("Sending shutdown_response"); 32 | serverSocket.SendMessage(replyMessage); 33 | 34 | _logger.LogInformation("Stopping shell"); 35 | _shell.Stop(); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/PowerShell-Kernel/bin/Debug/netcoreapp2.0/PowerShell-Kernel.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/PowerShell-Kernel", 16 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window 17 | "console": "internalConsole", 18 | "stopAtEntry": false, 19 | "internalConsoleOptions": "openOnSessionStart" 20 | }, 21 | { 22 | "name": ".NET Core Attach", 23 | "type": "coreclr", 24 | "request": "attach", 25 | "processId": "${command:pickProcess}" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Content/LanguageInfoContent.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Jupyter.Messages 7 | { 8 | public class LanguageInfoContent : Content 9 | { 10 | [JsonProperty("name")] 11 | public string Name { get; set; } 12 | 13 | [JsonProperty("version")] 14 | public string Version { get; set; } 15 | 16 | [JsonProperty("mimetype")] 17 | public string MimeType { get; set; } 18 | 19 | private string _extension; 20 | /// 21 | /// Extension including the dot, e.g. '.py' 22 | /// 23 | [JsonProperty("file_extension")] 24 | public string FileExtension { 25 | get { 26 | return _extension; 27 | } 28 | set { 29 | if (!value.StartsWith(".")) 30 | { 31 | _extension = "." + value; 32 | } 33 | else 34 | { 35 | _extension = value; 36 | } 37 | } 38 | } 39 | 40 | [JsonProperty("pygments_lexer")] 41 | public string PygmentsLexer { get; set; } 42 | 43 | [JsonProperty("codemirror_mode")] 44 | public string CodemirrorMode { get; set; } 45 | 46 | [JsonProperty("nbconvert_exporter")] 47 | public string NbConvertExporter { get; set; } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Source/Jupyter/Server/ConnectionInformation.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using Microsoft.Extensions.Logging; 4 | using Newtonsoft.Json; 5 | 6 | public class ConnectionInformation 7 | { 8 | [JsonProperty("stdin_port")] 9 | public int StdinPort { get; set; } 10 | 11 | [JsonProperty("ip")] 12 | public string IP { get; set; } 13 | 14 | [JsonProperty("control_port")] 15 | public int ControlPort { get; set; } 16 | 17 | [JsonProperty("hb_port")] 18 | public int HBPort { get; set; } 19 | 20 | [JsonProperty("signature_scheme")] 21 | public string SignatureScheme { get; set; } 22 | 23 | [JsonProperty("key")] 24 | public string Key { get; set; } 25 | 26 | [JsonProperty("shell_port")] 27 | public int ShellPort { get; set; } 28 | 29 | [JsonProperty("transport")] 30 | public string Transport { get; set; } 31 | 32 | [JsonProperty("iopub_port")] 33 | public int IOPubPort { get; set; } 34 | 35 | public static ConnectionInformation FromFile(string path, ILogger logger = null) 36 | { 37 | logger?.LogInformation("Opening file {0}", path); 38 | string fileContent = System.IO.File.ReadAllText(path); 39 | logger?.LogDebug(fileContent); 40 | 41 | return Deserialize(fileContent); 42 | } 43 | 44 | public static ConnectionInformation Deserialize(string json) 45 | { 46 | return JsonConvert.DeserializeObject(json); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Source/Jupyter/Server/Handlers/KernelInfoHandler.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Server.Handlers 2 | { 3 | using Microsoft.Extensions.Logging; 4 | using Jupyter.Messages; 5 | using NetMQ.Sockets; 6 | using Newtonsoft.Json; 7 | 8 | public class KernelInfoHandler : IMessageHandler 9 | { 10 | private readonly ILogger _logger; 11 | 12 | 13 | public KernelInfoHandler(ILogger logger) 14 | { 15 | _logger = logger; 16 | } 17 | 18 | public void HandleMessage(Message message, RouterSocket serverSocket, PublisherSocket ioPub) 19 | { 20 | Message replyMessage = new Message(MessageType.KernelInfoReply, CreateKernelInfoReply(), message.Header); 21 | 22 | _logger.LogInformation("Sending kernel_info_reply"); 23 | serverSocket.SendMessage(replyMessage); 24 | } 25 | 26 | private KernelInfoReplyContent CreateKernelInfoReply() 27 | { 28 | KernelInfoReplyContent kernelInfoReply = new KernelInfoReplyContent() 29 | { 30 | ProtocolVersion = "5.0", 31 | Implementation = "iPowerShell", 32 | ImplementationVersion = "0.0.1", 33 | LanguageInfo = new LanguageInfoContent() 34 | { 35 | Name = "PowerShell", 36 | Version = "5.0", 37 | MimeType = "text/powershell", 38 | FileExtension = ".ps1", 39 | PygmentsLexer = "powershell", 40 | CodemirrorMode = "powershell" 41 | } 42 | }; 43 | 44 | return kernelInfoReply; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /Source/tools/chocolateyuninstall.ps1: -------------------------------------------------------------------------------- 1 | # Use the .NET Core APIs to determine the current platform; if a runtime 2 | # exception is thrown, we are on FullCLR, not .NET Core. 3 | try { 4 | $Runtime = [System.Runtime.InteropServices.RuntimeInformation] 5 | $OSPlatform = [System.Runtime.InteropServices.OSPlatform] 6 | 7 | $IsCoreCLR = $true 8 | $IsLinux = $Runtime::IsOSPlatform($OSPlatform::Linux) 9 | $IsOSX = $Runtime::IsOSPlatform($OSPlatform::OSX) 10 | $IsWindows = $Runtime::IsOSPlatform($OSPlatform::Windows) 11 | } catch { 12 | # If these are already set read-only, we're on core, and done here 13 | try { 14 | $IsCoreCLR = $false 15 | $IsLinux = $false 16 | $IsOSX = $false 17 | $IsWindows = $true 18 | } catch { 19 | } 20 | } 21 | 22 | if ($IsWindows) { 23 | if (!$KernelFolder) { 24 | $KernelFolder = Join-Path $Env:AppData "jupyter\kernels\" 25 | } 26 | $Targets = @("Windows", "WindowsPowerShell") 27 | } 28 | if ($IsLinux) { 29 | if (!$KernelFolder) { 30 | $KernelFolder = "~/.local/share/jupyter/kernels" 31 | } 32 | $Targets = @("Linux") 33 | } 34 | if ($IsOSX) { 35 | if (!$KernelFolder) { 36 | $KernelFolder = "~/Library/Jupyter/kernels" 37 | } 38 | $Targets = @("Mac") 39 | } 40 | 41 | if (Test-Path $KernelFolder) { 42 | $path = Join-Path $KernelFolder "PowerShell" 43 | if (Test-Path $path) { 44 | Remove-Item $path -Recurse 45 | } 46 | 47 | if ($Targets -contains "WindowsPowerShell") { 48 | $path = Join-Path $KernelFolder "WindowsPowerShell" 49 | if (Test-Path $path) { 50 | Remove-Item $path -Recurse 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /Source/PowerShell-Kernel/ExecutionResult.cs: -------------------------------------------------------------------------------- 1 | using Jupyter.Messages; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Linq; 6 | using System.Management.Automation; 7 | using System.Text; 8 | 9 | namespace Jupyter.PowerShell 10 | { 11 | public class ErrorResult : Server.IErrorResult 12 | { 13 | public string Name { get; set; } 14 | public string Message { get; set; } 15 | public List StackTrace { get; set; } 16 | } 17 | 18 | public class ExecutionResult : Server.IExecutionResult 19 | { 20 | private PowerShellOptions _options; 21 | 22 | public ExecutionResult(PowerShellOptions options) 23 | { 24 | _options = options; 25 | } 26 | public List Output { get; } = new List(); 27 | 28 | public List Exceptions { get; } = new List(); 29 | 30 | public Server.IErrorResult Error { get; set; } 31 | 32 | public string OutputString { get; set; } 33 | 34 | public string OutputHtml { get; set; } 35 | 36 | public DisplayDataContent GetDisplayData() 37 | { 38 | var data = new Dictionary() 39 | { 40 | {"text/plain", OutputString} 41 | }; 42 | 43 | if(!string.IsNullOrEmpty(OutputHtml)) 44 | { 45 | data.Add("text/html", OutputHtml); 46 | } 47 | 48 | if (_options.JsonOutput && Output.Count > 0) 49 | { 50 | data.Add("application/json", new { output = Output }); 51 | } 52 | 53 | return new DisplayDataContent(data); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/Message.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Messages 2 | { 3 | using Newtonsoft.Json; 4 | using System.Collections.Generic; 5 | 6 | /// 7 | /// The base class for all Jupyter Messages 8 | /// 9 | public class Message 10 | { 11 | public Message(MessageType messageType, Content content, Header parentHeader) 12 | { 13 | UUID = parentHeader.Session; 14 | ParentHeader = parentHeader; 15 | Content = content; 16 | Header = new Header(messageType, parentHeader.Session); 17 | } 18 | 19 | public Message(MessageType messageType, Content content, Header parentHeader, List identifier, Header header, string hmac , Dictionary metaData) : 20 | this(messageType, content, parentHeader) 21 | { 22 | Identifiers = identifier; 23 | Header = header; 24 | HMac = hmac; 25 | MetaData = metaData; 26 | } 27 | 28 | [JsonProperty("identifiers")] 29 | public List Identifiers { get; set; } = new List(); 30 | 31 | [JsonProperty("uuid")] 32 | public string UUID { get; set; } = string.Empty; 33 | 34 | [JsonProperty("hmac")] 35 | public string HMac { get; set; } = string.Empty; 36 | 37 | [JsonProperty("header")] 38 | public Header Header { get; set; } 39 | 40 | [JsonProperty("parent_header")] 41 | public Header ParentHeader { get; set; } 42 | 43 | [JsonProperty("metadata")] 44 | public Dictionary MetaData { get; set; } = new Dictionary(); 45 | 46 | [JsonProperty("content")] 47 | public Content Content { get; set; } = new Content(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Source/Jupyter/Jupyter.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | ..\ 4 | $(SolutionDir)\Output\$(Configuration)\ 5 | 6 | 7 | 8 | netcoreapp2.0 9 | A .NET Framewok library for making Jupyter kernels 10 | Jupyter-Kernel 11 | Joel "Jaykul" Bennett 12 | HuddledMasses.org 13 | Jupyter Kernel .NET 14 | Copyright (c) 2017 Joel Bennett, Zohaib Rauf 15 | http://www.apache.org/licenses/LICENSE-2.0 16 | https://github.com/Jaykul/Jupyter-PowerShell 17 | https://github.com/Jaykul/Jupyter-PowerShell 18 | en 19 | $(VersionSuffix) 20 | 1.0.0 21 | $(Version)$(VersionSuffix) 22 | 23 | 24 | 2.0.0 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jaykul/powershell:dotnet-sdk-stretch as builder 2 | 3 | # Use array to avoid Docker prepending /bin/sh -c 4 | COPY ./Source /root/Source 5 | COPY ./build.ps1 /root/ 6 | RUN pwsh /root/build.ps1 -Platform Linux 7 | 8 | FROM jupyter/base-notebook:92fe05d1e7e5 as run 9 | 10 | LABEL maintainer="Joel Bennett " \ 11 | org.label-schema.schema-version="1.0" \ 12 | org.label-schema.name="jupyter-powershell" \ 13 | description="This Dockerfile includes jupyter and the Jupyter-PowerShell kernel." 14 | 15 | # TODO: add LABELs: 16 | # readme.md="https://github.com/PowerShell/PowerShell/blob/master/docker/README.md" \ 17 | # org.label-schema.usage="https://github.com/PowerShell/PowerShell/tree/master/docker#run-the-docker-image-you-built" \ 18 | # org.label-schema.url="https://github.com/PowerShell/PowerShell/blob/master/docker/README.md" \ 19 | # org.label-schema.vcs-url="https://github.com/PowerShell/PowerShell" \ 20 | # org.label-schema.vcs-ref=${VCS_REF} 21 | # org.label-schema.version=${POWERSHELL_VERSION} \ 22 | # org.label-schema.docker.cmd="docker run ${IMAGE_NAME} pwsh -c '$psversiontable'" \ 23 | 24 | USER root 25 | 26 | RUN apt-get update \ 27 | && DEBIAN_FRONTEND=noninteractive \ 28 | apt-get install -y --no-install-recommends \ 29 | libc6 libgcc1 libgssapi-krb5-2 liblttng-ust0 libstdc++6 libcurl3 libunwind8 libuuid1 zlib1g libssl1.0.0 libicu55 \ 30 | # && localedef -i ${LANGUAGE} -c -f UTF-8 -A /usr/share/locale/locale.alias ${LANGUAGE}.UTF-8 \ 31 | && rm -rf /var/lib/apt/lists/* 32 | 33 | COPY --from=builder /root/Output/Release/Linux /usr/src/jupyter-powershell 34 | COPY --from=builder /root/Output/Release/Linux/kernel.json /usr/local/share/jupyter/kernels/powershell/kernel.json 35 | 36 | # # Make sure the contents of our repo are in ${HOME} 37 | # COPY . ${HOME}/ 38 | # RUN conda install -y -c damianavila82 rise 39 | # RUN chown -R ${NB_UID} ${HOME} \ 40 | # && chmod +x /usr/src/jupyter-powershell/PowerShell-Kernel \ 41 | # && sed -i -e "s.PowerShell-Kernel./usr/src/jupyter-powershell/PowerShell-Kernel." /usr/local/share/jupyter/kernels/powershell/kernel.json 42 | # USER ${NB_USER} -------------------------------------------------------------------------------- /Source/Jupyter-PowerShell.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26621.2 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jupyter", "Jupyter\Jupyter.csproj", "{D89EF375-F71D-4D12-BD9C-38B98D2A5CEA}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8D3207F2-04F6-46AF-A3AD-E3F2AB648672}" 9 | ProjectSection(SolutionItems) = preProject 10 | .gitattributes = .gitattributes 11 | .gitignore = .gitignore 12 | Install.ps1 = Install.ps1 13 | kernel.json = kernel.json 14 | LICENSE.md = LICENSE.md 15 | nuget.config = nuget.config 16 | ReadMe.ipynb = ReadMe.ipynb 17 | ReadMe.md = ReadMe.md 18 | EndProjectSection 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerShell-Kernel", "PowerShell-Kernel\PowerShell-Kernel.csproj", "{7848247B-60E1-4C32-809F-5184D938D8EA}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {D89EF375-F71D-4D12-BD9C-38B98D2A5CEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {D89EF375-F71D-4D12-BD9C-38B98D2A5CEA}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {D89EF375-F71D-4D12-BD9C-38B98D2A5CEA}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {D89EF375-F71D-4D12-BD9C-38B98D2A5CEA}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {7848247B-60E1-4C32-809F-5184D938D8EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {7848247B-60E1-4C32-809F-5184D938D8EA}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {7848247B-60E1-4C32-809F-5184D938D8EA}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {7848247B-60E1-4C32-809F-5184D938D8EA}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {7A59C3E1-7114-4849-901C-2CDB42CCF1B8} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /Source/Jupyter/Server/Heartbeat.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Server 2 | { 3 | using NetMQ; 4 | using NetMQ.Sockets; 5 | using System.Threading; 6 | using Microsoft.Extensions.Logging; 7 | 8 | public class Heartbeat 9 | { 10 | private ILogger _logger; 11 | private string _address; 12 | private ResponseSocket _socket; 13 | private ManualResetEventSlim _stopEvent; 14 | private Thread _thread; 15 | private bool _disposed; 16 | 17 | public Heartbeat(ILogger logger, string address) 18 | { 19 | _logger = logger; 20 | _address = address; 21 | 22 | _socket = new ResponseSocket(); 23 | _stopEvent = new ManualResetEventSlim(); 24 | } 25 | 26 | public void Start() 27 | { 28 | _thread = new Thread(StartServerLoop); 29 | _thread.Start(); 30 | //ThreadPool.QueueUserWorkItem(new WaitCallback(StartServerLoop)); 31 | } 32 | 33 | public void Stop() 34 | { 35 | _stopEvent.Set(); 36 | } 37 | 38 | public ManualResetEventSlim GetWaitEvent() 39 | { 40 | return _stopEvent; 41 | } 42 | 43 | private void StartServerLoop(object state) 44 | { 45 | _socket.Bind(_address); 46 | 47 | while (!_stopEvent.Wait(0)) 48 | { 49 | byte[] data = _socket.ReceiveFrameBytes(); 50 | 51 | _logger.LogInformation(System.Text.Encoding.Default.GetString(data)); 52 | // Echoing back whatever was received 53 | _socket.TrySendFrame(data); 54 | } 55 | } 56 | 57 | public void Dispose() 58 | { 59 | Dispose(true); 60 | } 61 | 62 | protected void Dispose(bool dispose) 63 | { 64 | if (!_disposed) 65 | { 66 | if (dispose) 67 | { 68 | if (_socket != null) 69 | { 70 | _socket.Dispose(); 71 | } 72 | 73 | _disposed = true; 74 | } 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Source/Jupyter/Messages/MessageType.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Converters; 3 | using System.Runtime.Serialization; 4 | 5 | namespace Jupyter.Messages 6 | { 7 | [JsonConverter(typeof(StringEnumConverter))] 8 | public enum MessageType 9 | { 10 | [EnumMember(Value = "execute_request")] 11 | ExecuteRequest, 12 | [EnumMember(Value = "execute_input")] 13 | ExecuteInput, 14 | [EnumMember(Value = "execute_reply")] 15 | ExecuteReply, 16 | [EnumMember(Value = "execute_result")] 17 | ExecuteResult, 18 | 19 | [EnumMember(Value = "display_data")] 20 | DisplayData, 21 | [EnumMember(Value = "update_display_data")] 22 | UpdateDisplayData, 23 | 24 | [EnumMember(Value = "kernel_info_request")] 25 | KernelInfoRequest, 26 | [EnumMember(Value = "kernel_info_reply")] 27 | KernelInfoReply, 28 | 29 | [EnumMember(Value = "comm_open")] 30 | CommOpen, 31 | [EnumMember(Value = "comm_msg")] 32 | CommMessage, 33 | [EnumMember(Value = "comm_close")] 34 | CommClose, 35 | [EnumMember(Value = "comm_info")] 36 | CommInfo, 37 | [EnumMember(Value = "comm_info_request")] 38 | CommInfoRequest, 39 | [EnumMember(Value = "comm_info_reply")] 40 | CommInfoReply, 41 | 42 | [EnumMember(Value = "inspect_request")] 43 | InspectRequest, 44 | [EnumMember(Value = "inspect_reply")] 45 | InspectReply, 46 | 47 | [EnumMember(Value = "clear_output")] 48 | ClearOutput, 49 | 50 | [EnumMember(Value = "complete_request")] 51 | CompleteRequest, 52 | [EnumMember(Value = "complete_reply")] 53 | CompleteReply, 54 | 55 | [EnumMember(Value = "shutdown_request")] 56 | ShutDownRequest, 57 | [EnumMember(Value = "shutdown_reply")] 58 | ShutDownReply, 59 | 60 | [EnumMember(Value = "status")] 61 | Status, 62 | 63 | [EnumMember(Value = "pyout")] 64 | Output, 65 | 66 | [EnumMember(Value = "pyin")] 67 | Input, 68 | 69 | [EnumMember(Value = "error")] 70 | Error, 71 | 72 | [EnumMember(Value = "stream")] 73 | Stream, 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Source/Jupyter/Server/JsonHelper.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Serialization; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Reflection; 10 | 11 | namespace Jupyter.Server 12 | { 13 | public class JsonHelper 14 | { 15 | 16 | private class CustomJsonTextWriter : JsonTextWriter 17 | { 18 | public CustomJsonTextWriter(TextWriter textWriter) : base(textWriter) { } 19 | 20 | public int CurrentDepth { get; private set; } 21 | 22 | public override void WriteStartObject() 23 | { 24 | CurrentDepth++; 25 | base.WriteStartObject(); 26 | } 27 | 28 | public override void WriteEndObject() 29 | { 30 | CurrentDepth--; 31 | base.WriteEndObject(); 32 | } 33 | } 34 | 35 | private class CustomContractResolver : DefaultContractResolver 36 | { 37 | private readonly Func _includeProperty; 38 | 39 | public CustomContractResolver(Func includeProperty) 40 | { 41 | _includeProperty = includeProperty; 42 | } 43 | 44 | protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) 45 | { 46 | var property = base.CreateProperty(member, memberSerialization); 47 | var shouldSerialize = property.ShouldSerialize; 48 | property.ShouldSerialize = obj => _includeProperty() && (shouldSerialize == null || shouldSerialize(obj)); 49 | return property; 50 | } 51 | } 52 | 53 | public static string SerializeObject(object obj, int maxDepth) 54 | { 55 | using (var strWriter = new StringWriter()) 56 | { 57 | using (var jsonWriter = new CustomJsonTextWriter(strWriter)) 58 | { 59 | Func include = () => jsonWriter.CurrentDepth <= maxDepth; 60 | var resolver = new CustomContractResolver(include); 61 | var serializer = new JsonSerializer { ContractResolver = resolver }; 62 | serializer.Serialize(jsonWriter, obj); 63 | } 64 | return strWriter.ToString(); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | This project is archived. Try Microsoft's reimplementation in [dotnet/interactive](https://github.com/dotnet/interactive/tree/main/src/Microsoft.DotNet.Interactive.PowerShell). 2 | 3 | # A [Jupyter](https://jupyter.org/) Kernel for [PowerShell](https://github.com/PowerShell/PowerShell) 4 | 5 | Create PowerShell notebooks in a web browser, with commands and captured output. Add markdown blocks for documentation! 6 | 7 | You can use Jupyter with PowerShell to produce documentation of your troubleshooting, researching, and even your regular processes. You can also use HTML and Javascript with your PowerShell data to create visual reports, do numerical analysis, etc. 8 | 9 | ## Get it for yourself 10 | 11 | The easiest way to try the kernel is using Binder. You can just click here: [![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/jaykul/Jupyter-PowerShell/master) 12 | 13 | The next easiest way is using docker (if you have docker installed). You can start a copy like this: 14 | 15 | ```posh 16 | docker run -it --rm -p 8888:8888 jaykul/powershell-notebook-base 17 | ``` 18 | 19 | You can also install the kernel locally (assuming you have Jupyter or a clone installed) using [chocolatey](http://chocolatey.org/): 20 | 21 | ```posh 22 | choco install jupyter-powershell 23 | ``` 24 | 25 | ## Current Status 26 | 27 | The PowerShell kernel is based on PowerShell Core, in order to be cross-platform and standalone. 28 | 29 | At this point, I'm only handling two messages: 30 | 31 | * KernelInfo request 32 | * Execute request 33 | 34 | ### Features 35 | 36 | Since Jupyter is all about interaction and documentation, if you want details about the features, you can read the [Features](https://github.com/Jaykul/Jupyter-PowerShell/blob/master/Features.ipynb) notebook here on github, or by running the binder link above. 37 | 38 | ## PowerShell Core 39 | 40 | In order to get cross-platform support, this kernel is based on [PowerShell Core](https://github.com/PowerShell/PowerShell). 41 | 42 | To build it yourself --or to run the "PowerShell (Core)" kernel-- you need [dotnet core 2](https://www.microsoft.com/net/core). You can build it by running `dotnet restore; dotnet build` from the root. If you want to build it in Visual Studio, you need VS 2017 version 15.3 or higher. 43 | 44 | ## A Note on the Jupyter library 45 | 46 | This kernel is being written in C#, and in the process, I've taken some messaging code from the [iCSharp kernel](https://github.com/zabirauf/icsharp) and made a generic library for .Net with a re-usable core for anyone who needs to create [Jupyter](https://jupyter.org/) kernels in .Net languages -- so feel free to borrow that if you like (it's under the Apache license). 47 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Source/Jupyter/Session.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter 2 | { 3 | using Jupyter.Messages; 4 | using Jupyter.Server; 5 | using Jupyter.Server.Handlers; 6 | using Microsoft.Extensions.Logging; 7 | using NetMQ.Sockets; 8 | using System.Collections.Generic; 9 | 10 | public class Session : IKernelSocketProvider 11 | { 12 | private ILogger _logger; 13 | private ConnectionInformation _connection; 14 | private IReplEngine _engine; 15 | private Validator _validator; 16 | private Shell _shell; 17 | private Heartbeat _heartbeat; 18 | private Dictionary _messageHandlers; 19 | 20 | public Session(ConnectionInformation connection, IReplEngine engine, ILogger logger) 21 | { 22 | _connection = connection; 23 | _logger = logger; 24 | _validator = new Validator(_logger, connection.Key, connection.SignatureScheme); 25 | MessageSender.Validator = _validator; 26 | MessageSender.Logger = _logger; 27 | 28 | _engine = engine; 29 | 30 | InitializeMessageHandlers(); 31 | 32 | _heartbeat = new Heartbeat(_logger, GetAddress(connection.HBPort)); 33 | _shell = new Shell(_logger, GetAddress(connection.ShellPort), GetAddress(connection.IOPubPort), _validator, MessageHandlers); 34 | 35 | _heartbeat.Start(); 36 | _shell.Start(); 37 | } 38 | 39 | public RouterSocket ShellSocket { get => _shell.ShellSocket; } 40 | public PublisherSocket PublishSocket { get => _shell.PublishSocket; } 41 | 42 | public void Wait() 43 | { 44 | _shell.GetWaitEvent().Wait(); 45 | _heartbeat.GetWaitEvent().Wait(); 46 | } 47 | 48 | 49 | private Dictionary MessageHandlers => this._messageHandlers; 50 | 51 | private void InitializeMessageHandlers() 52 | { 53 | this._messageHandlers = new Dictionary 54 | { 55 | { MessageType.KernelInfoRequest, new KernelInfoHandler(_logger) }, 56 | { MessageType.ExecuteRequest, new ExecuteRequestHandler(_logger, _engine) }, 57 | { MessageType.ShutDownRequest, new ShutdownHandler(_logger, _heartbeat, _shell) } 58 | }; 59 | // this._messageHandlers.Add(MessageTypeValues.CompleteRequest, new CompleteRequestHandler()); 60 | } 61 | 62 | private string GetAddress(int port) 63 | { 64 | string address = string.Format("{0}://{1}:{2}", _connection.Transport, _connection.IP, port); 65 | _logger.LogDebug(address); 66 | return address; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Source/PowerShell-Kernel/ScalarHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Text; 5 | 6 | namespace Jupyter.PowerShell 7 | { 8 | public static class ScalarHelper 9 | { 10 | public static bool IsScalar(Collection typeNames) 11 | { 12 | if (typeNames.Count == 0) 13 | { 14 | return false; 15 | } 16 | 17 | // NOTE: we do not use inheritance here, since we deal with 18 | // value types or with types where inheritance is not a factor for the selection 19 | string typeName = typeNames[0]; 20 | 21 | if (string.IsNullOrEmpty(typeName)) 22 | { 23 | return false; 24 | } 25 | 26 | if (typeName.StartsWith("Deserialized.", StringComparison.OrdinalIgnoreCase)) 27 | { 28 | typeName = typeName.Substring("Deserialized.".Length); 29 | if (string.IsNullOrEmpty(typeName)) 30 | { 31 | return false; 32 | } 33 | } 34 | 35 | // check if the type is derived from a System.Enum 36 | if (!(typeNames.Count < 2 || string.IsNullOrEmpty(typeNames[1])) && String.Equals(typeNames[1], "System.Enum", StringComparison.Ordinal)) 37 | { 38 | return true; 39 | } 40 | 41 | return s_defaultScalarTypesHash.Contains(typeName); 42 | } 43 | 44 | static ScalarHelper() 45 | { 46 | s_defaultScalarTypesHash.Add("System.String"); 47 | s_defaultScalarTypesHash.Add("System.SByte"); 48 | s_defaultScalarTypesHash.Add("System.Byte"); 49 | s_defaultScalarTypesHash.Add("System.Int16"); 50 | s_defaultScalarTypesHash.Add("System.UInt16"); 51 | s_defaultScalarTypesHash.Add("System.Int32"); 52 | s_defaultScalarTypesHash.Add("System.UInt32"); 53 | s_defaultScalarTypesHash.Add("System.Int64"); 54 | s_defaultScalarTypesHash.Add("System.UInt64"); 55 | s_defaultScalarTypesHash.Add("System.Char"); 56 | s_defaultScalarTypesHash.Add("System.Single"); 57 | s_defaultScalarTypesHash.Add("System.Double"); 58 | s_defaultScalarTypesHash.Add("System.Boolean"); 59 | s_defaultScalarTypesHash.Add("System.Decimal"); 60 | s_defaultScalarTypesHash.Add("System.IntPtr"); 61 | s_defaultScalarTypesHash.Add("System.Security.SecureString"); 62 | s_defaultScalarTypesHash.Add("System.Numerics.BigInteger"); 63 | } 64 | 65 | private static readonly HashSet s_defaultScalarTypesHash = new HashSet(StringComparer.OrdinalIgnoreCase); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Source/tools/chocolateyinstall.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | # The path to put our kernel.json folders in 4 | $KernelFolder, 5 | 6 | # The path where the kernel executables are (should contain at least the 'Windows' folder) 7 | $InstallPath 8 | ) 9 | # Use the .NET Core APIs to determine the current platform; if a runtime 10 | # exception is thrown, we are on FullCLR, not .NET Core. 11 | try { 12 | $Runtime = [System.Runtime.InteropServices.RuntimeInformation] 13 | $OSPlatform = [System.Runtime.InteropServices.OSPlatform] 14 | 15 | $IsCoreCLR = $true 16 | $IsLinux = $Runtime::IsOSPlatform($OSPlatform::Linux) 17 | $IsOSX = $Runtime::IsOSPlatform($OSPlatform::OSX) 18 | $IsWindows = $Runtime::IsOSPlatform($OSPlatform::Windows) 19 | } catch { 20 | # If these are already set read-only, we're on core, and done here 21 | try { 22 | $IsCoreCLR = $false 23 | $IsLinux = $false 24 | $IsOSX = $false 25 | $IsWindows = $true 26 | } catch { 27 | } 28 | } 29 | 30 | if (!$InstallPath) { 31 | $InstallPath = Split-Path $PSScriptRoot 32 | } 33 | 34 | if ($IsWindows) { 35 | if (!$KernelFolder) { 36 | $KernelFolder = Join-Path $Env:AppData "Jupyter\kernels\" 37 | } 38 | $Targets = @("Windows\PowerShell-Kernel.exe") #, "WindowsPowerShell") 39 | } 40 | if($IsLinux) { 41 | if (!$KernelFolder) { 42 | $KernelFolder = "~/.local/share/jupyter/kernels" 43 | } 44 | $Targets = @("Linux/PowerShell-Kernel") 45 | } 46 | if($IsOSX) { 47 | if (!$KernelFolder) { 48 | $KernelFolder = "~/Library/Jupyter/kernels" 49 | } 50 | $Targets = @("Mac/PowerShell-Kernel") 51 | } 52 | 53 | 54 | foreach($target in $Targets) { 55 | $kernelPath = Join-Path $InstallPath $target 56 | 57 | if (!(Test-Path $kernelPath -PathType Leaf)) { 58 | Write-Warning " 59 | Can't find the $target PowerShell kernel file in: 60 | $kernelPath 61 | 62 | Expected the $target kernel to be in the same folder with this script. 63 | 64 | If you're running this from source code, you must first build using build.ps1 65 | Then you can re-run the copy of this file WITHIN the build output ... 66 | `n 67 | " 68 | continue 69 | } 70 | # Necessary for windows only: 71 | $kernelPath = Resolve-Path $kernelPath 72 | $kernelPath = $kernelPath -replace "\\", "\\" 73 | 74 | $targetName = if ($target -match "WindowsPowerShell") { "WindowsPowerShell" } else { "PowerShell" } 75 | $kernelFile = Join-Path $kernelFolder "$targetName/kernel.json" 76 | 77 | # Make sure the kernel folder exists 78 | $null = New-Item -Path (Split-Path $kernelFile) -Force -ItemType Directory 79 | 80 | $kernelData = @( 81 | "{" 82 | " ""argv"": [" 83 | " ""$kernelPath""," 84 | " ""{connection_file}""" 85 | " ]," 86 | " ""display_name"": ""$targetName""," 87 | " ""language"": ""PowerShell""" 88 | "}" 89 | ) -join "`n" 90 | 91 | Set-Content -Path $kernelFile -Value $kernelData 92 | } 93 | -------------------------------------------------------------------------------- /Source/PowerShell-Kernel/Kernel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Jupyter.Messages; 3 | using System; 4 | using System.Linq; 5 | using Microsoft.Extensions.Configuration; 6 | using System.IO; 7 | using Jupyter.Config; 8 | using System.Reflection; 9 | 10 | namespace Jupyter.PowerShell 11 | { 12 | public class Kernel 13 | { 14 | private static ILogger logger; 15 | private static IConfigurationRoot configuration; 16 | 17 | public static void Main(string[] args) 18 | { 19 | var loggerFactory = new LoggerFactory(); 20 | 21 | var installPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 22 | var baseConfig = Path.Combine(installPath, "PowerShell-Kernel.Config.json"); 23 | var cwdConfig = Path.Combine(Directory.GetCurrentDirectory(), "PowerShell-Kernel.Config.json"); 24 | 25 | var configBuilder = new ConfigurationBuilder() 26 | .AddJsonFile(baseConfig, true) 27 | .AddJsonFile(cwdConfig, true); 28 | 29 | configuration = configBuilder.Build(); 30 | 31 | var loggerOptions = new LoggerOptions(); 32 | configuration.GetSection("Logger").Bind(loggerOptions); 33 | 34 | if (configuration.GetSection("Debug").GetValue("BreakOnStart")) 35 | { 36 | if (System.Diagnostics.Debugger.IsAttached) 37 | { 38 | System.Diagnostics.Debugger.Break(); 39 | } 40 | else 41 | { 42 | System.Diagnostics.Debugger.Launch(); 43 | } 44 | } 45 | 46 | if (loggerOptions.ConsoleOutput) 47 | { 48 | loggerFactory.AddConsole(); 49 | } 50 | 51 | if(loggerOptions.DebuggerOutput) 52 | { 53 | loggerFactory.AddDebug(); 54 | } 55 | 56 | logger = loggerFactory.CreateLogger(); 57 | 58 | PrintAllArgs(args); 59 | if (args.Length <= 0) 60 | { 61 | Console.WriteLine("Requires path to Connection file."); 62 | return; 63 | } 64 | 65 | ConnectionInformation connectionInformation = ConnectionInformation.FromFile(args[0]); 66 | 67 | var options = new PowerShellOptions(); 68 | configuration.GetSection("PowerShell").Bind(options); 69 | 70 | var engine = new PowerShellEngine(options, logger); 71 | Session connection = new Session(connectionInformation, engine, logger); 72 | engine.AddReadOnlyVariables(("JupyterSession", connection)); 73 | connection.Wait(); 74 | 75 | System.Threading.Thread.Sleep(60000); 76 | } 77 | 78 | private static void PrintAllArgs(string[] args) 79 | { 80 | logger.LogDebug("PowerShell Jupyter Kernel Args: "); 81 | foreach (string s in args) 82 | { 83 | logger.LogDebug(s); 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /Source/Jupyter/Validator.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Security.Cryptography; 6 | using System.Text; 7 | using Microsoft.Extensions.Logging; 8 | using Jupyter.Messages; 9 | using Newtonsoft.Json; 10 | 11 | public class Validator 12 | { 13 | private readonly ILogger _logger; 14 | 15 | private readonly HMAC _signatureGenerator; 16 | 17 | private readonly Encoding _encoder; 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | /// Shared key used to initialize the digest. 23 | public Validator(ILogger logger, string key, string algorithm) 24 | { 25 | _logger = logger; 26 | _encoder = new UTF8Encoding(); 27 | algorithm = algorithm.Replace("-", "").ToUpperInvariant(); 28 | _logger.LogDebug(algorithm + ": '" + key + "'"); 29 | try 30 | { 31 | switch(algorithm) 32 | { 33 | case "HMACSHA256": 34 | _signatureGenerator = new HMACSHA256(); 35 | break; 36 | 37 | default: 38 | _signatureGenerator = HMAC.Create(algorithm); 39 | break; 40 | } 41 | } 42 | catch(Exception ex) 43 | { 44 | throw new ArgumentException("Failed to create an algorithm for " + algorithm, "algorithm", ex); 45 | } 46 | this._signatureGenerator.Key = this._encoder.GetBytes(key); 47 | } 48 | 49 | /// 50 | /// Creates the signature. 51 | /// 52 | /// The signature. 53 | /// Message. 54 | public string CreateSignature(params string[] messages) 55 | { 56 | byte[] sourceBytes; 57 | _signatureGenerator.Initialize(); 58 | // For all items update the signature 59 | var last = messages.Length - 1; 60 | for (var i = 0; i < last; i++) 61 | { 62 | sourceBytes = this._encoder.GetBytes(messages[i]); 63 | _signatureGenerator.TransformBlock(sourceBytes, 0, sourceBytes.Length, null, 0); 64 | } 65 | 66 | sourceBytes = _encoder.GetBytes(messages[last]); 67 | _signatureGenerator.TransformFinalBlock(sourceBytes, 0, sourceBytes.Length); 68 | 69 | // Calculate the digest and remove - 70 | return BitConverter.ToString(_signatureGenerator.Hash).Replace("-", "").ToLower(); 71 | } 72 | 73 | /// 74 | /// Determines whether this instance is valid signature the specified message. 75 | /// 76 | /// true 77 | /// false 78 | /// Message. 79 | public bool IsValidSignature(string hash, params string[] messages) 80 | { 81 | string calculatedSignature = this.CreateSignature(messages); 82 | this._logger.LogInformation(string.Format("Expected Signature: {0}", calculatedSignature)); 83 | return string.Equals(hash, calculatedSignature, StringComparison.OrdinalIgnoreCase); 84 | } 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /Source/PowerShell-Kernel/PowerShell-Kernel.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | ..\ 4 | $(SolutionDir)\Output\$(Configuration)\ 5 | 6 | 7 | Exe 8 | PowerShell-Kernel 9 | Jupyter.PowerShell 10 | 11 | Jupyter.PowerShell.Kernel 12 | 13 | 14 | netcoreapp2.0 15 | A PowerShell kernel for Jupyter 16 | Jupyter-PowerShell 17 | Joel "Jaykul" Bennett 18 | HuddledMasses.org 19 | Jupyter PowerShell 20 | Copyright (c) 2017 Joel Bennett 21 | http://www.apache.org/licenses/LICENSE-2.0 22 | https://github.com/Jaykul/Jupyter-PowerShell 23 | https://github.com/Jaykul/Jupyter-PowerShell 24 | en-US 25 | $(VersionSuffix) 26 | 1.0.0 27 | $(Version)$(VersionSuffix) 28 | NETCORE 29 | 30 | 31 | 32 | linux-x64;osx.10.12-x64;win7-x64 33 | 2.0.0 34 | 35 | 36 | 37 | x64 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | PreserveNewest 69 | 70 | 71 | PreserveNewest 72 | 73 | 74 | PreserveNewest 75 | 76 | 77 | -------------------------------------------------------------------------------- /Source/Jupyter/Server/Shell.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Server 2 | { 3 | 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Linq; 7 | using System.Threading; 8 | using Microsoft.Extensions.Logging; 9 | using NetMQ; 10 | using NetMQ.Sockets; 11 | using Jupyter.Messages; 12 | using Newtonsoft.Json; 13 | using Jupyter.Server.Handlers; 14 | 15 | public class Shell : IKernelSocketProvider 16 | { 17 | private ILogger logger; 18 | private string addressShell; 19 | private string addressIOPub; 20 | 21 | private Validator signatureValidator; 22 | private RouterSocket serverSocket; 23 | private PublisherSocket ioPubSocket; 24 | 25 | private ManualResetEventSlim stopEvent; 26 | 27 | private Thread thread; 28 | private bool disposed; 29 | 30 | private Dictionary messageHandlers; 31 | 32 | public RouterSocket ShellSocket { get => serverSocket; } 33 | public PublisherSocket PublishSocket { get => ioPubSocket; } 34 | 35 | 36 | public Shell(ILogger logger, 37 | string addressShell, 38 | string addressIOPub, 39 | Validator signatureValidator, 40 | Dictionary messageHandlers) 41 | { 42 | this.logger = logger; 43 | this.addressShell = addressShell; 44 | this.addressIOPub = addressIOPub; 45 | this.signatureValidator = signatureValidator; 46 | this.messageHandlers = messageHandlers; 47 | 48 | this.serverSocket = new RouterSocket(); 49 | this.ioPubSocket = new PublisherSocket(); 50 | this.stopEvent = new ManualResetEventSlim(); 51 | } 52 | 53 | public void Start() 54 | { 55 | this.thread = new Thread(this.StartServerLoop); 56 | this.thread.Start(); 57 | 58 | this.logger.LogInformation("Shell Started"); 59 | //ThreadPool.QueueUserWorkItem(new WaitCallback(StartServerLoop)); 60 | } 61 | 62 | private void StartServerLoop(object state) 63 | { 64 | this.serverSocket.Bind(this.addressShell); 65 | this.logger.LogInformation(string.Format("Bound the Shell server to address {0}", this.addressShell)); 66 | 67 | this.ioPubSocket.Bind(this.addressIOPub); 68 | this.logger.LogInformation(string.Format("Bound IOPub to address {0}", this.addressIOPub)); 69 | 70 | while (!this.stopEvent.Wait(0)) 71 | { 72 | if (serverSocket.ReceiveMessage() is Message message) 73 | { 74 | this.logger.LogInformation(JsonConvert.SerializeObject(message)); 75 | 76 | if (this.messageHandlers.TryGetValue(message.Header.MessageType, out IMessageHandler handler)) 77 | { 78 | this.logger.LogInformation(string.Format("Sending message to handler {0}", message.Header.MessageType)); 79 | handler.HandleMessage(message, this.serverSocket, this.ioPubSocket); 80 | this.logger.LogInformation("Message handling complete"); 81 | } 82 | else 83 | { 84 | this.logger.LogWarning(string.Format("No message handler found for message type {0}", 85 | message.Header.MessageType)); 86 | } 87 | } 88 | } 89 | } 90 | 91 | public void Stop() 92 | { 93 | this.stopEvent.Set(); 94 | } 95 | 96 | public ManualResetEventSlim GetWaitEvent() 97 | { 98 | return this.stopEvent; 99 | } 100 | 101 | public void Dispose() 102 | { 103 | this.Dispose(true); 104 | } 105 | 106 | protected void Dispose(bool dispose) 107 | { 108 | if (!this.disposed) 109 | { 110 | if (dispose) 111 | { 112 | if (this.serverSocket != null) 113 | { 114 | this.serverSocket.Dispose(); 115 | } 116 | 117 | if (this.ioPubSocket != null) 118 | { 119 | this.ioPubSocket.Dispose(); 120 | } 121 | 122 | this.disposed = true; 123 | } 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /jupyter-powershell.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | jupyter-powershell 21 | 22 | 23 | 24 | 1.0.0 25 | https://github.com/Jaykul/Jupyter-PowerShell/releases 26 | 27 | Joel "Jaykul" Bennett 28 | 29 | 30 | 31 | 32 | Jupyter-PowerShell 33 | Joel "Jaykul" Bennett 34 | 35 | https://github.com/Jaykul/Jupyter-PowerShell 36 | 37 | Copyright (c) 2017 Joel Bennett 38 | 39 | https://github.com/Jaykul/Jupyter-PowerShell/blob/master/LICENSE.md 40 | false 41 | https://github.com/Jaykul/Jupyter-PowerShell 42 | 43 | 44 | https://github.com/Jaykul/Jupyter-PowerShell/issues 45 | jupyter kernel powershell notebook 46 | A PowerShell kernel for Jupyter 47 | A PowerShell Kernel for Jupyter (and nteract, etc.) 48 | 49 | 50 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /LiterateDevOps.md: -------------------------------------------------------------------------------- 1 | --- 2 | theme : "League" 3 | transition: "slide" 4 | --- 5 | 6 | # Literate DevOps 7 | ## with Jupyter 8 | ## & PowerShell 9 | 10 | Some thoughts and tools 11 | 12 | by Joel "Jaykul" Bennett 13 | 14 | --- 15 | 16 | # Who Am I? 17 | 18 | * Hacker and Programmer 19 | * Social Science Major 20 | * Automation Specialist 21 | * Software Engineer 22 | * DevOps Consultant 23 | * Microsoft PowerShell MVP 24 | 25 | 26 | 27 | 28 | -- 29 | 30 | # Who Am I? 31 | 32 | So in other words 33 | 34 | ```text 35 | I'm software engineer with a sociology background, 36 | doing #DevOps in the Windows (and .NET) world, 37 | wishing we'd stop taking the learning curve for granted. 38 | ``` 39 | 40 | 41 | --- 42 | 43 | ## [Literate Programming](http://literateprogramming.com/) 44 | 45 | A 1984 book by Donald Knuth
46 | Goal: significantly better documentation 47 | 48 | 1. Explain to humans what we want the computer to do 49 | 2. Write as an essayist, carefully naming and explaining 50 | 3. Order the program for human comprehension 51 | 52 | note: 53 | 54 | I believe that the time is ripe for significantly better documentation of programs, and that we can best achieve this by considering programs to be works of literature. Hence, my title: "Literate Programming." 55 | 56 | Let us change our traditional attitude to the construction of programs: Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to human beings what we want a computer to do. 57 | 58 | The practitioner of literate programming can be regarded as an essayist, whose main concern is with exposition and excellence of style. Such an author, with thesaurus in hand, chooses the names of variables carefully and explains what each variable means. He or she strives for a program that is comprehensible because its concepts have been introduced in an order that is best for human understanding, using a mixture of formal and informal methods that reinforce each other. 59 | 60 | 61 | -- 62 | ## Literate Programming 63 | 64 | Tools (tangle and weave) were written... 65 | 66 | But the examples you'll find today are all demos, parts of a book or article on the topic. 67 | 68 | Needless to say, it never really took off. 69 | 70 | ### So why bring it up? 71 | 72 | note: 73 | 74 | Writing a literate program is a lot more work than writing a normal program. After all, who ever documents their programs in the first place!? Moreover, who documents them in a pedagogical style that is easy to understand? And finally, who _ever_ provides commentary on the theory and design issues behind the code as they write the documentation? 75 | 76 | 77 | -- 78 | ## Literate Programming 79 | 80 | > I believe that the time is ripe for significantly better documentation 81 | 82 | There are new tools, new audiences 83 | 84 | * Emacs Org Mode 85 | * IPython => Jupyter 86 | * nteract, Spyder IDE, Atom Hydrogen 87 | * Apache Zeppelin 88 | * Jupyter Labs 89 | 90 | --- 91 | ## Literate DevOps 92 | 93 | [Howard Abrams](http://www.howardism.org/Technical/Emacs/literate-devops.html) and [Marc Hoffman](https://archive.fosdem.org/2016/schedule/event/literate_devops_for_configuration_management/)
like to talk about DevOps as bi-modal: 94 | 95 | 1. Bang head until server works 96 | 2. Capture effort into automation 97 | 98 | 99 | We want to make 1. more like 2. 100 | 101 | -- 102 | 103 | ## Literate DevOps 104 | 105 | We want to capture the process of investigating and learning so that: 106 | 107 | 1. Others can learn from our bruises 108 | 2. We can export it to the automation 109 | 110 | --- 111 | 112 | ## Juptyer Notebooks 113 | 114 | Since the goal is to capture the investigation,
115 | I basically have to do one of two things: 116 | 117 | 1. Take copious notes 118 | 2. Enable transcription 119 | 120 |
121 | 122 | For now, let's talk about taking notes
123 | (but don't forget to ask me about transcription later) 124 | 125 | -- 126 | ## Jupyter Notebooks 127 | 128 | **Remember** this is _not_ for application code, it's 129 | * Infrastructure 130 | * Deployments 131 | 132 | I'm not writting an essay about my grandiose plans (yet), but I am simply taking notes during my investigation. I need a tool that's good for that. 133 | 134 | * I need a notebook 135 | * I like markdown 136 | * I want code inline 137 | * But I 138 | 139 | 140 | 141 | -- 142 | # Jupyter Demo 143 | 144 | Allow me to introduce Jupyter 145 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | .ipynb_checkpoints/ 263 | Output/ 264 | Source/Output/ 265 | -------------------------------------------------------------------------------- /Source/Jupyter/Server/MessageSender.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Server 2 | { 3 | using Jupyter.Messages; 4 | using Microsoft.Extensions.Logging; 5 | using NetMQ; 6 | using Newtonsoft.Json; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | using System.Linq; 10 | 11 | public static class MessageSender 12 | { 13 | private static readonly JsonSerializerSettings _ignoreLoops = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; 14 | 15 | public static Validator Validator { get; set; } 16 | public static ILogger Logger { get; set; } 17 | 18 | private static string SessionId; 19 | private static Header lastParentHeader; 20 | 21 | public static bool SendMessage(this NetMQSocket socket, Message message) 22 | { 23 | Logger?.LogTrace("Sending Message: {0}", JsonConvert.SerializeObject(message)); 24 | if(message.Header.MessageType == MessageType.Input) 25 | { 26 | lastParentHeader = message.ParentHeader; 27 | } 28 | if (string.IsNullOrEmpty(message.UUID)) 29 | { 30 | message.UUID = SessionId; 31 | } 32 | 33 | if (string.IsNullOrEmpty(message.Header.Session)) 34 | { 35 | message.Header.Session = SessionId; 36 | message.ParentHeader = lastParentHeader; 37 | } 38 | 39 | var messageFrames = new[] { 40 | JsonConvert.SerializeObject(message.Header), 41 | JsonConvert.SerializeObject(message.ParentHeader), 42 | JsonConvert.SerializeObject(message.MetaData), 43 | JsonConvert.SerializeObject(message.Content) 44 | }; 45 | string hmac = Validator.CreateSignature(messageFrames); 46 | 47 | if (message.Identifiers != null && message.Identifiers.Count > 0) 48 | { 49 | // Send ZMQ identifiers from the message we're responding to. 50 | // This is important when we're dealing with ROUTER sockets, like the shell socket, 51 | // because the message won't be sent unless we manually include these. 52 | foreach (var ident in message.Identifiers) 53 | { 54 | socket.TrySendFrame(ident, true); 55 | } 56 | } 57 | else 58 | { 59 | // This is just a normal message so send the UUID 60 | socket.SendFrame(message.UUID, true); 61 | } 62 | 63 | socket.SendFrame(Constants.DELIMITER, true); 64 | socket.SendFrame(hmac, true); 65 | socket.SendFrame(messageFrames[0], true); 66 | socket.SendFrame(messageFrames[1], true); 67 | socket.SendFrame(messageFrames[2], true); 68 | socket.SendFrame(messageFrames[3], false); 69 | 70 | return true; 71 | } 72 | 73 | public static Message ReceiveMessage(this NetMQSocket socket) 74 | { 75 | var identifier = socket.ReceiveFrameIdentifier(); 76 | var hmac = socket.ReceiveFrameString(); 77 | var headerFrame = socket.ReceiveFrameString(); 78 | var parentFrame = socket.ReceiveFrameString(); 79 | var metadataFrame = socket.ReceiveFrameString(); 80 | var contentFrame = socket.ReceiveFrameString(); 81 | 82 | if (!Validator.IsValidSignature(hmac, headerFrame, parentFrame, metadataFrame, contentFrame)) 83 | { 84 | return null; 85 | } 86 | 87 | var header = JsonConvert.DeserializeObject
(headerFrame); 88 | Content content; 89 | 90 | switch (header.MessageType) 91 | { 92 | case MessageType.ExecuteRequest: 93 | content = JsonConvert.DeserializeObject(contentFrame); 94 | break; 95 | case MessageType.CompleteRequest: 96 | content = JsonConvert.DeserializeObject(contentFrame); 97 | break; 98 | case MessageType.ShutDownRequest: 99 | content = JsonConvert.DeserializeObject(contentFrame); 100 | break; 101 | case MessageType.KernelInfoRequest: 102 | content = JsonConvert.DeserializeObject(contentFrame); 103 | break; 104 | //case MessageType.ExecuteInput: 105 | //case MessageType.ExecuteReply: 106 | //case MessageType.ExecuteResult: 107 | //case MessageType.CompleteReply: 108 | //case MessageType.ShutDownReply: 109 | //case MessageType.KernelInfoReply: 110 | 111 | //case MessageType.Status: 112 | //case MessageType.Output: 113 | //case MessageType.Input: 114 | //case MessageType.Error: 115 | //case MessageType.Stream: 116 | default: 117 | Logger?.LogWarning(header.MessageType + " message not handled."); 118 | content = new Content(); 119 | break; 120 | } 121 | 122 | // Update the static session 123 | SessionId = header.Session; 124 | 125 | return new Message(header.MessageType, content, JsonConvert.DeserializeObject
(parentFrame), 126 | identifier, header, hmac, JsonConvert.DeserializeObject>(metadataFrame)); 127 | } 128 | 129 | private static List ReceiveFrameIdentifier(this NetMQSocket socket) 130 | { 131 | // There may be additional ZMQ identities attached; read until the delimiter " 132 | // and store them in message.identifiers 133 | // http://ipython.org/ipython-doc/dev/development/messaging.html#the-wire-protocol 134 | byte[] delimiterBytes = Encoding.ASCII.GetBytes(Constants.DELIMITER); 135 | byte[] delimiter; 136 | var identifier = new List(); 137 | do 138 | { 139 | delimiter = socket.ReceiveFrameBytes(); 140 | identifier.Add(delimiter); 141 | } while (!delimiter.SequenceEqual(delimiterBytes)); 142 | // strip delimiter 143 | identifier.RemoveAt(identifier.Count - 1); 144 | return identifier; 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Source/PowerShell-Kernel/Commands/WriteJupyterCommand.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Jupyter.PowerShell.Commands 3 | { 4 | using Jupyter.Messages; 5 | using Jupyter.Server; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Linq; 8 | using System.Collections; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Management.Automation; 12 | 13 | [Cmdlet(VerbsCommunications.Write, "Jupyter", DefaultParameterSetName = "Automatic")] 14 | public class WriteJupyterCommand : PSCmdlet 15 | { 16 | // MIME Types implemented in the base Jupyter notebook 17 | readonly IReadOnlyDictionary types = new Dictionary() 18 | { 19 | {"text", "text/plain"}, 20 | {"html", "text/html"}, 21 | {"markdown","text/markdown"}, 22 | {"latex","text/latex"}, 23 | {"json","application/json"}, 24 | {"javascript","application/javascript"}, 25 | {"png","image/png"}, 26 | {"jpeg","image/jpeg"}, 27 | {"svg","image/svg+xml"} 28 | }; 29 | 30 | [Parameter(ValueFromPipeline =true, Mandatory = true)] 31 | [ValidateNotNullOrEmpty()] 32 | public PSObject InputObject { get; set; } 33 | 34 | [Parameter(ParameterSetName = "IdDisplay", Mandatory = true)] 35 | public string Id { get; set; } 36 | 37 | [Parameter(ParameterSetName = "UpdateDisplay", Mandatory = true)] 38 | public string Update { get; set; } 39 | 40 | [Parameter()] 41 | public Hashtable Metadata { get; set; } 42 | 43 | [Parameter()] 44 | [ValidateSet("text", "html", "markdown", "latex", "json", "javascript", "svg", "png", "jpeg")] 45 | public string MimeType { get; set; } 46 | 47 | private object Normalize(object value, string mimetype) 48 | { 49 | if(value is PSObject) 50 | { 51 | value = ((PSObject)value).BaseObject; 52 | } 53 | 54 | if (!(value is string)) 55 | { 56 | 57 | if (value is IEnumerable) 58 | { 59 | value = string.Join("\r\n", value); 60 | } 61 | else if (value is IEnumerable && ((IEnumerable)value).All(o => o is string || o is PSObject && ((PSObject)o).BaseObject is string)) 62 | { 63 | value = string.Join("\r\n", ((IEnumerable)value).Select(o => o.ToString())); 64 | } 65 | } 66 | 67 | if (mimetype.ToLowerInvariant().EndsWith("json")) 68 | { 69 | try 70 | { 71 | return JToken.Parse(value.ToString()); 72 | } 73 | catch 74 | { 75 | return value; 76 | } 77 | } 78 | return value; 79 | } 80 | protected override void BeginProcessing() 81 | { 82 | base.BeginProcessing(); 83 | 84 | if (!string.IsNullOrEmpty(MimeType)) 85 | { 86 | MimeType = types[MimeType]; 87 | } 88 | } 89 | 90 | protected override void ProcessRecord() 91 | { 92 | base.ProcessRecord(); 93 | 94 | var data = new Dictionary(); 95 | var isJupyterData = false; 96 | 97 | // if they override the mimetype, force the issue 98 | if (!string.IsNullOrEmpty(MimeType)) 99 | { 100 | isJupyterData = true; 101 | data.Add(MimeType, Normalize(InputObject.BaseObject, MimeType)); 102 | } 103 | else 104 | { 105 | foreach (var property in InputObject.Properties) 106 | { 107 | var name = property.Name.ToLower(); 108 | if (types.Keys.Contains(name)) 109 | { 110 | isJupyterData = true; 111 | data.Add(types[name], Normalize(property.Value, types[name])); 112 | } 113 | else if (name.Contains('/')) 114 | { 115 | isJupyterData = true; 116 | data.Add(name, Normalize(property.Value, name)); 117 | } 118 | } 119 | } 120 | 121 | if(!isJupyterData) 122 | { 123 | if (InputObject.BaseObject is IDictionary dictionary) 124 | { 125 | foreach (var property in dictionary.Keys) 126 | { 127 | var name = property.ToString().ToLower(); 128 | if (types.Keys.Contains(name)) 129 | { 130 | isJupyterData = true; 131 | data.Add(types[name], Normalize(dictionary[property], types[name])); 132 | } 133 | else if (name.Contains('/')) 134 | { 135 | isJupyterData = true; 136 | data.Add(name, Normalize(dictionary[property], name)); 137 | } 138 | } 139 | } 140 | } 141 | 142 | if (!isJupyterData) 143 | { 144 | if (InputObject.BaseObject is string) 145 | { 146 | data.Add("text/plain", Normalize(InputObject.BaseObject, "text/plain")); 147 | } 148 | else 149 | { 150 | data.Add("application/json", Normalize(InputObject.BaseObject, "application/json")); 151 | } 152 | } 153 | 154 | var content = new DisplayDataContent(data); 155 | if (Metadata != null) 156 | { 157 | foreach (var key in Metadata.Keys) 158 | { 159 | content.MetaData.Add(key.ToString(), Metadata[key]); 160 | } 161 | } 162 | 163 | var type = MessageType.DisplayData; 164 | 165 | if (!string.IsNullOrEmpty(Id)) 166 | { 167 | content.Transient.Add("display_id", Update); 168 | } 169 | if (!string.IsNullOrEmpty(Update)) 170 | { 171 | type = MessageType.UpdateDisplayData; 172 | content.Transient.Add("display_id", Update); 173 | } 174 | 175 | var session = SessionState.PSVariable.GetValue("JupyterSession") as Session; 176 | 177 | Message message = new Message(type, content, new Header(type, null)); 178 | 179 | session.PublishSocket.SendMessage(message); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /Source/Jupyter/Server/Handlers/ExecuteHandler.cs: -------------------------------------------------------------------------------- 1 | namespace Jupyter.Server.Handlers 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Web; 9 | using Microsoft.Extensions.Logging; 10 | using Jupyter.Messages; 11 | using NetMQ.Sockets; 12 | using Newtonsoft.Json; 13 | 14 | public class ExecuteRequestHandler : IMessageHandler 15 | { 16 | private readonly ILogger _logger; 17 | private readonly IReplEngine _replEngine; 18 | 19 | private int _executionCount = 1; 20 | 21 | public ExecuteRequestHandler(ILogger logger, IReplEngine replEngine) 22 | { 23 | _logger = logger; 24 | _replEngine = replEngine; 25 | } 26 | 27 | public void HandleMessage(Message request, RouterSocket serverSocket, PublisherSocket ioPub) 28 | { 29 | _logger.LogDebug(string.Format("Message Content {0}", request.Content)); 30 | ExecuteRequestContent executeRequest = request.Content as ExecuteRequestContent; 31 | 32 | _logger.LogInformation(string.Format("Execute Request received with code {0}", executeRequest.Code)); 33 | 34 | // 1: Send Busy status on IOPub 35 | PublishStatus(request, ioPub, KernelState.Busy); 36 | 37 | // 2: Send execute input on IOPub 38 | PublishInput(request, ioPub, executeRequest.Code); 39 | 40 | // 3: Call the engine with the code 41 | string code = executeRequest.Code; 42 | var results = _replEngine.Execute(code); 43 | 44 | // 4: Send execute reply to shell socket 45 | // 5: Send execute result message to IOPub 46 | if (results.Error != null) 47 | { 48 | // SendExecuteErrorMessage(message, serverSocket, results.Error); 49 | PublishError(request, ioPub, results.Error); 50 | } 51 | else 52 | { 53 | SendExecuteReplyMessage(request, serverSocket); 54 | if (results.Output.Any()) 55 | { 56 | // 5: Send execute result message to IOPub 57 | DisplayDataContent displayData = results.GetDisplayData(); 58 | PublishOutput(request, ioPub, displayData); 59 | } 60 | } 61 | 62 | // 6: Send IDLE status message to IOPub 63 | this.PublishStatus(request, ioPub, KernelState.Idle); 64 | 65 | // TODO: History 66 | // The Jupyter Notebook interface does not use history messages 67 | // However, we're supposed to be storing the history *with output* 68 | // So that a HistoryHandler can find it when asked 69 | if (executeRequest.StoreHistory) 70 | { 71 | this._executionCount += 1; 72 | } 73 | } 74 | 75 | public void PublishStatus(Message request, PublisherSocket ioPub, KernelState statusValue) 76 | { 77 | Message message = new Message(MessageType.Status, new StatusContent(statusValue), request.Header); 78 | 79 | this._logger.LogInformation(string.Format("Sending message to IOPub {0}", JsonConvert.SerializeObject(message))); 80 | ioPub.SendMessage(message); 81 | this._logger.LogInformation("Message Sent"); 82 | } 83 | 84 | public void PublishOutput(Message request, PublisherSocket ioPub, DisplayDataContent data) 85 | { 86 | var content = new ExecuteResultPublishContent(data, _executionCount); 87 | Message message = new Message(MessageType.ExecuteResult, content, request.Header); 88 | 89 | this._logger.LogInformation(string.Format("Sending message to IOPub {0}", JsonConvert.SerializeObject(message))); 90 | ioPub.SendMessage(message); 91 | } 92 | 93 | public void PublishInput(Message request, PublisherSocket ioPub, string code) 94 | { 95 | var content = new ExecuteRequestPublishContent(code, _executionCount); 96 | Message message = new Message(MessageType.Input, content, request.Header); 97 | 98 | this._logger.LogInformation(string.Format("Sending message to IOPub {0}", JsonConvert.SerializeObject(message))); 99 | ioPub.SendMessage(message); 100 | } 101 | 102 | public void SendExecuteReplyMessage(Message request, RouterSocket shellSocket) 103 | { 104 | var content = new ExecuteResultReplyContent() 105 | { 106 | ExecutionCount = this._executionCount, 107 | Payload = new List>(), 108 | UserExpressions = new Dictionary() 109 | }; 110 | 111 | Message message = new Message(MessageType.ExecuteReply, content, request.Header) 112 | { 113 | // Stick the original identifiers on the message so they'll be sent first 114 | // Necessary since the shell socket is a ROUTER socket 115 | Identifiers = request.Identifiers 116 | }; 117 | 118 | this._logger.LogInformation(string.Format("Sending message to Shell {0}", JsonConvert.SerializeObject(message))); 119 | shellSocket.SendMessage(message); 120 | } 121 | 122 | public void SendExecuteErrorMessage(Message request, RouterSocket shellSocket, IErrorResult error) 123 | { 124 | var content = new ExecuteErrorReplyContent() 125 | { 126 | ExecutionCount = _executionCount, 127 | //EName = error.Name, 128 | //EValue = error.Message, 129 | //Traceback = error.StackTrace 130 | }; 131 | 132 | Message message = new Message(MessageType.ExecuteReply, content, request.Header) 133 | { 134 | // Stick the original identifiers on the message so they'll be sent first 135 | // Necessary since the shell socket is a ROUTER socket 136 | Identifiers = request.Identifiers 137 | }; 138 | 139 | this._logger.LogInformation(string.Format("Sending message to Shell {0}", JsonConvert.SerializeObject(message))); 140 | shellSocket.SendMessage(message); 141 | } 142 | 143 | private void PublishError(Message request, PublisherSocket ioPub, IErrorResult error) 144 | { 145 | // Write to Stderr first -- then write the ExecuteError 146 | var errorMessage = new StderrContent(error.Message); 147 | Message message = new Message(MessageType.Stream, errorMessage, request.Header) 148 | { 149 | Identifiers = request.Identifiers 150 | }; 151 | 152 | this._logger.LogInformation(string.Format("Sending message to IOPub {0}", JsonConvert.SerializeObject(message))); 153 | ioPub.SendMessage(message); 154 | 155 | 156 | var executeReply = new ExecuteErrorPublishContent() 157 | { 158 | ExecutionCount = _executionCount, 159 | EName = error.Name, 160 | EValue = error.Message, 161 | Traceback = error.StackTrace 162 | }; 163 | message = new Message(MessageType.Error, executeReply, request.Header) 164 | { 165 | Identifiers = request.Identifiers 166 | }; 167 | this._logger.LogInformation(string.Format("Sending message to IOPub {0}", JsonConvert.SerializeObject(message))); 168 | ioPub.SendMessage(message); 169 | 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /Release.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/markdown": [ 11 | "# How to build the Jupyter PowerShell Kernel\n", 12 | "\n", 13 | "Start in the Jupyter-PowerShell folder, and make sure there's no output from the last run" 14 | ] 15 | }, 16 | "metadata": {}, 17 | "output_type": "display_data" 18 | } 19 | ], 20 | "source": [ 21 | "$ProjectDirectory = $Pwd.Path\n", 22 | "if($ProjectDirectory -match \"Jupyter-PowerShell$\") {\n", 23 | " @\"\n", 24 | "# How to build the Jupyter PowerShell Kernel\n", 25 | "\n", 26 | "Start in the Jupyter-PowerShell folder, and make sure there's no output from the last run\n", 27 | "\"@ | Write-Jupyter -Mimetype markdown\n", 28 | "} else {\n", 29 | " \"# This notebook only works in the Jupyter-PowerShell project folder\" | \n", 30 | " Write-Jupyter -Mimetype markdown\n", 31 | " Write-Error \"You cannot build Jupyter from here\"\n", 32 | " $ProjectDirectory = $Env:Temp\n", 33 | "}" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 2, 39 | "metadata": { 40 | "collapsed": true 41 | }, 42 | "outputs": [], 43 | "source": [ 44 | "rm (Join-Path $ProjectDirectory Output\\Release) -recurse" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "## To build the project\n", 52 | "\n", 53 | "We really just need to `dotnet restore` and `dotnet build` --although currently, we require a very specific version (2.0.0-preview2-006502) of the dotnet CLI tools because we depend on the latest PowerShell Core bits, and they're _utterly_ incompatible with anything else.\n", 54 | "\n", 55 | "### Increment the version\n", 56 | "\n", 57 | "The project files to use the new `--version-suffix` feature. To make sure your build isn't confused with an official one, you need to specify a suffix, like \"-local-preview\".\n", 58 | "\n", 59 | "## To package the project\n", 60 | "\n", 61 | "In order to ship something, we need to `publish` it -- this includes the `build` step, so we can just call it directly.\n", 62 | "\n", 63 | "Once we've published, we need to package it with Chocolatey." 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 3, 69 | "metadata": {}, 70 | "outputs": [ 71 | { 72 | "data": { 73 | "text/plain": [ 74 | "Restoring packages for C:\\Users\\Joel\\Projects\\Jupyter\\Jupyter-PowerShell\\Jupyter\\Jupyter.csproj...\r\n", 75 | " Restoring packages for C:\\Users\\Joel\\Projects\\Jupyter\\Jupyter-PowerShell\\PowerShell-Kernel\\PowerShell-Kernel.csproj...\r\n", 76 | " Restore completed in 715.68 ms for C:\\Users\\Joel\\Projects\\Jupyter\\Jupyter-PowerShell\\Jupyter\\Jupyter.csproj.\r\n", 77 | " Restore completed in 1.26 sec for C:\\Users\\Joel\\Projects\\Jupyter\\Jupyter-PowerShell\\PowerShell-Kernel\\PowerShell-Kernel.csproj." 78 | ] 79 | }, 80 | "execution_count": 3, 81 | "metadata": {}, 82 | "output_type": "execute_result" 83 | } 84 | ], 85 | "source": [ 86 | "dotnet restore" 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": 4, 92 | "metadata": {}, 93 | "outputs": [ 94 | { 95 | "data": { 96 | "text/plain": [ 97 | "Microsoft (R) Build Engine version 15.3.388.41745 for .NET Core\r\n", 98 | "Copyright (C) Microsoft Corporation. All rights reserved.\r\n", 99 | "\r\n", 100 | " Jupyter -> C:\\Users\\Joel\\Projects\\Jupyter\\Jupyter-PowerShell\\Output\\Release\\netcoreapp2.0\\Jupyter.dll\r\n", 101 | " Jupyter -> C:\\Users\\Joel\\Projects\\Jupyter\\Jupyter-PowerShell\\Output\\Release\\netcoreapp2.0\\publish\\\r\n", 102 | " PowerShell-Kernel -> C:\\Users\\Joel\\Projects\\Jupyter\\Jupyter-PowerShell\\Output\\Release\\netcoreapp2.0\\PowerShell-Kernel.dll\r\n", 103 | " PowerShell-Kernel -> C:\\Users\\Joel\\Projects\\Jupyter\\Jupyter-PowerShell\\Output\\Release\\netcoreapp2.0\\publish\\" 104 | ] 105 | }, 106 | "execution_count": 4, 107 | "metadata": {}, 108 | "output_type": "execute_result" 109 | } 110 | ], 111 | "source": [ 112 | "dotnet publish -f netcoreapp2.0 -c Release --version-suffix \"-beta-5\"" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": 5, 118 | "metadata": {}, 119 | "outputs": [ 120 | { 121 | "data": { 122 | "text/plain": [ 123 | "Microsoft (R) Build Engine version 15.3.388.41745 for .NET Core\r\n", 124 | "Copyright (C) Microsoft Corporation. All rights reserved.\r\n", 125 | "\r\n", 126 | " Jupyter -> C:\\Users\\Joel\\Projects\\Jupyter\\Jupyter-PowerShell\\Output\\Release\\net462\\Jupyter.dll\r\n", 127 | " Jupyter -> C:\\Users\\Joel\\Projects\\Jupyter\\Jupyter-PowerShell\\Output\\Release\\net462\\publish\\\r\n", 128 | " PowerShell-Kernel -> C:\\Users\\Joel\\Projects\\Jupyter\\Jupyter-PowerShell\\Output\\Release\\net462\\PowerShell-Kernel.exe\r\n", 129 | " PowerShell-Kernel -> C:\\Users\\Joel\\Projects\\Jupyter\\Jupyter-PowerShell\\Output\\Release\\net462\\publish\\" 130 | ] 131 | }, 132 | "execution_count": 5, 133 | "metadata": {}, 134 | "output_type": "execute_result" 135 | } 136 | ], 137 | "source": [ 138 | "dotnet publish -f net462 -c Release --version-suffix \"-beta-5\"" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": 6, 144 | "metadata": { 145 | "collapsed": true 146 | }, 147 | "outputs": [], 148 | "source": [ 149 | "# We're just trying to rename the folders so that we can hash them:\n", 150 | "Move-Item Output\\Release\\net462\\publish Output\\Release\\PowerShell-Full\n", 151 | "Move-Item Output\\Release\\netcoreapp2.0\\publish Output\\Release\\PowerShell-Core\n", 152 | "Copy-Item tools Output\\Release -Recurse\n", 153 | "Remove-Item Output\\Release\\net462 -Recurse\n", 154 | "Remove-Item Output\\Release\\netcoreapp2.0 -Recurse" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": 7, 160 | "metadata": {}, 161 | "outputs": [ 162 | { 163 | "data": { 164 | "text/plain": [ 165 | "Mode LastWriteTime Length Name \r\n", 166 | "---- ------------- ------ ---- \r\n", 167 | "-a---- 7/17/2017 12:53 AM 103522 Jupyter-PowerShell.cat" 168 | ] 169 | }, 170 | "execution_count": 7, 171 | "metadata": {}, 172 | "output_type": "execute_result" 173 | } 174 | ], 175 | "source": [ 176 | "# Now generate the file catalog\n", 177 | "New-FileCatalog -CatalogFilePath Output\\Release\\tools\\Jupyter-PowerShell.cat -Path Output\\Release\\" 178 | ] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "execution_count": 8, 183 | "metadata": {}, 184 | "outputs": [ 185 | { 186 | "data": { 187 | "text/plain": [ 188 | "Directory: C:\\Users\\Joel\\Projects\\Jupyter\\Jupyter-PowerShell\\Output\\Release\\tools\r\n", 189 | "\r\n", 190 | "\r\n", 191 | "SignerCertificate Status Path \r\n", 192 | "----------------- ------ ---- \r\n", 193 | "DC8A5C5FAFFBCFFE7F60552B49ED1A0DDC145482 Valid Jupyter-PowerShell.cat" 194 | ] 195 | }, 196 | "execution_count": 8, 197 | "metadata": {}, 198 | "output_type": "execute_result" 199 | } 200 | ], 201 | "source": [ 202 | "# Maybe sign the catalog\n", 203 | "if(Get-Module Authenticode -List) {\n", 204 | " Authenticode\\Set-AuthenticodeSignature Output\\Release\\tools\\Jupyter-PowerShell.cat\n", 205 | "}" 206 | ] 207 | }, 208 | { 209 | "cell_type": "code", 210 | "execution_count": 9, 211 | "metadata": {}, 212 | "outputs": [ 213 | { 214 | "data": { 215 | "text/plain": [ 216 | "Chocolatey v0.10.7\r\n", 217 | "Attempting to build package from 'jupyter-powershell.nuspec'.\r\n", 218 | "Successfully created package 'Output\\Release\\jupyter-powershell.1.0.0-beta-5.nupkg'" 219 | ] 220 | }, 221 | "execution_count": 9, 222 | "metadata": {}, 223 | "output_type": "execute_result" 224 | } 225 | ], 226 | "source": [ 227 | "C:\\ProgramData\\chocolatey\\choco.exe pack --outputdirectory Output\\Release" 228 | ] 229 | }, 230 | { 231 | "cell_type": "code", 232 | "execution_count": null, 233 | "metadata": { 234 | "collapsed": true 235 | }, 236 | "outputs": [], 237 | "source": [] 238 | } 239 | ], 240 | "metadata": { 241 | "anaconda-cloud": {}, 242 | "kernelspec": { 243 | "display_name": "PowerShell (Full)", 244 | "language": "PowerShell", 245 | "name": "powershell-full" 246 | }, 247 | "language_info": { 248 | "codemirror_mode": "powershell", 249 | "file_extension": ".ps1", 250 | "mimetype": "text/powershell", 251 | "name": "PowerShell", 252 | "nbconvert_exporter": null, 253 | "pygments_lexer": "powershell", 254 | "version": "5.0" 255 | } 256 | }, 257 | "nbformat": 4, 258 | "nbformat_minor": 1 259 | } 260 | -------------------------------------------------------------------------------- /Source/PowerShell-Kernel/PowerShellEngine.cs: -------------------------------------------------------------------------------- 1 | using Jupyter.Server; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Management.Automation; 8 | using System.Management.Automation.Runspaces; 9 | using System.Reflection; 10 | 11 | namespace Jupyter.PowerShell 12 | { 13 | public class PowerShellEngine : IReplEngine 14 | { 15 | private readonly ILogger _logger; 16 | private readonly PowerShellOptions _options; 17 | 18 | /// 19 | /// The shared initial session state (we'll preload modules, etc). 20 | /// 21 | public InitialSessionState Iss { get; private set; } 22 | 23 | public Runspace Runspace { get; private set; } 24 | 25 | public PowerShellEngine(PowerShellOptions options, ILogger logger) 26 | { 27 | _options = options; 28 | _logger = logger; 29 | Iss = InitialSessionState.CreateDefault2(); 30 | 31 | // Preload all cmdlets from this assembly 32 | Assembly core = Assembly.GetExecutingAssembly(); 33 | Iss.LoadCmdlets(core); 34 | 35 | // FOR CORE: 36 | // Fix the PSModulePath, because now we're a full-blown host and ship with our own modules 37 | // TODO: What should this default be? 38 | var oldPath = Environment.GetEnvironmentVariable("PSModulePath", EnvironmentVariableTarget.Process) ?? ""; 39 | var localModules = Path.Combine(Path.GetDirectoryName(core.Location), "Modules"); 40 | var newPath = string.Join(Path.PathSeparator, oldPath.Split(Path.PathSeparator).Append(localModules).Distinct()); 41 | Environment.SetEnvironmentVariable("PSModulePath", newPath, EnvironmentVariableTarget.Process); 42 | 43 | // We may want to use a runspace pool? ps.RunspacePool = rsp; 44 | Runspace = RunspaceFactory.CreateRunspace(Iss); 45 | Runspace.Open(); 46 | } 47 | 48 | 49 | /// 50 | /// Adds read only variables to the shared initial session state. 51 | /// 52 | /// A collection of string tuples containing the name, value, and description of the variables to be added. 53 | public void AddReadOnlyVariables(params (string Name, object Value)[] values) 54 | { 55 | var readOnly = ScopedItemOptions.Constant & ScopedItemOptions.ReadOnly; 56 | foreach (var v in values) 57 | { 58 | var variable = new PSVariable(v.Name, v.Value, readOnly); 59 | Runspace.SessionStateProxy.PSVariable.Set(variable); 60 | } 61 | } 62 | 63 | 64 | public IExecutionResult Execute(string script) 65 | { 66 | var result = new ExecutionResult(_options); 67 | Pipeline pipeline = null; 68 | IEnumerable output = null; 69 | try 70 | { 71 | pipeline = Runspace.CreatePipeline(); 72 | pipeline.Commands.AddScript(script); 73 | output = pipeline.Invoke().Where(o => o != null); 74 | 75 | LogErrors(result, pipeline.Error); 76 | } 77 | catch (RuntimeException err) 78 | { 79 | if (result.Error == null) 80 | { 81 | var errorRecord = err.ErrorRecord; 82 | if (errorRecord != null) 83 | { 84 | result.Error = new ErrorResult() 85 | { 86 | Name = errorRecord.FullyQualifiedErrorId, 87 | Message = string.Format( 88 | "{0} : {1}\r\n", 89 | errorRecord.InvocationInfo?.InvocationName, 90 | errorRecord.ToString()), 91 | StackTrace = new List(new[] { 92 | errorRecord.InvocationInfo?.PositionMessage, 93 | #if NETCORE 94 | errorRecord.ScriptStackTrace, 95 | #endif 96 | "CategoryInfo : " + errorRecord.CategoryInfo, 97 | "FullyQualifiedErrorId : " + errorRecord.FullyQualifiedErrorId }) 98 | }; 99 | } 100 | 101 | } 102 | _logger.LogError("PowerShell Exception in ExecuteRequest {0}:\r\n{1}\r\n{2}", script, err.Message, err.StackTrace); 103 | result.Exceptions.Add(err); 104 | } 105 | catch (Exception ex) 106 | { 107 | if (result.Error == null) 108 | { 109 | result.Error = new ErrorResult() 110 | { 111 | Name = ex.GetType().FullName, 112 | Message = string.Format( 113 | "{0} : {1}", 114 | ex.Source, 115 | ex.Message), 116 | StackTrace = new List(ex.StackTrace.Split(new[] { "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries)) 117 | }; 118 | } 119 | _logger.LogError("Unhandled PowerShell Exception in ExecuteRequest {0}:\r\n{1}\r\n{2}", script, ex.Message, ex.StackTrace); 120 | result.Exceptions.Add(ex); 121 | } 122 | 123 | CollectOutput(result, output); 124 | 125 | return result; 126 | } 127 | 128 | private void CollectOutput(ExecutionResult result, IEnumerable output) 129 | { 130 | if (output != null && output.Any()) 131 | { 132 | result.Output.AddRange(output.Select(o => o.BaseObject)); 133 | try 134 | { 135 | Pipeline pipeline = Runspace.CreatePipeline(); 136 | var formatter = new Command("Out-String"); 137 | formatter.Parameters.Add("Width", 120); 138 | pipeline.Commands.Add(formatter); 139 | 140 | result.OutputString = string.Join("\n", pipeline.Invoke(output).Select(line => line.ToString())).Trim(); 141 | 142 | //pipeline = Runspace.CreatePipeline(); 143 | //formatter = new Command("ConvertTo-Json"); 144 | //pipeline.Commands.Add(formatter); 145 | //result.OutputJson = string.Join("\n", pipeline.Invoke(JsonWrapper.Wrap(script, output)).Select(line => line.ToString().Replace("\r\n","\n"))); 146 | 147 | // Users need to output their own HTML, ConvertTo-Html is *way* too flawed. 148 | // BUGBUG: need a better way to detect html? 149 | if (output.First().BaseObject is string && result.OutputString.StartsWith("<") && result.OutputString.EndsWith(">")) 150 | { 151 | result.OutputHtml = result.OutputString; 152 | } 153 | } 154 | catch (Exception ex) 155 | { 156 | _logger.LogError("Unhandled PowerShell Exception in ExecuteRequest {0}:\r\n{1}\r\n{2}", "Out-String", ex.Message, ex.StackTrace); 157 | } 158 | } 159 | } 160 | 161 | private static void LogErrors(ExecutionResult result, PipelineReader errorStream) 162 | { 163 | if (errorStream?.Count > 0) 164 | { 165 | foreach (object error in errorStream.ReadToEnd()) 166 | { 167 | var pso = error as PSObject; 168 | if (pso == null) 169 | { 170 | continue; 171 | } 172 | ErrorRecord errorRecord = pso.BaseObject as ErrorRecord; 173 | Exception exception; 174 | 175 | if (errorRecord != null) 176 | { 177 | if (result.Error == null) 178 | { 179 | result.Error = new ErrorResult() 180 | { 181 | Name = errorRecord.FullyQualifiedErrorId, 182 | Message = string.Format( 183 | "{0} : {1}", 184 | errorRecord.InvocationInfo.InvocationName, 185 | errorRecord.ToString()).TrimEnd('\r','\n') + "\r\n", 186 | StackTrace = new List(new[] { 187 | errorRecord.InvocationInfo.PositionMessage, 188 | #if NETCORE 189 | errorRecord.ScriptStackTrace, 190 | #endif 191 | "CategoryInfo : " + errorRecord.CategoryInfo, 192 | "FullyQualifiedErrorId : " + errorRecord.FullyQualifiedErrorId }) 193 | }; 194 | } 195 | exception = errorRecord.Exception; 196 | } 197 | else 198 | { 199 | exception = pso.BaseObject as Exception; 200 | } 201 | 202 | if (exception != null) 203 | { 204 | result.Exceptions.Add(exception); 205 | } 206 | } 207 | } 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /nteract - plotly.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# The API returns a page of 10 (by default)" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": { 14 | "inputHidden": false, 15 | "outputHidden": false 16 | }, 17 | "outputs": [ 18 | { 19 | "data": { 20 | "text/html": [ 21 | "\r\n", 22 | "\r\n", 23 | "\r\n", 24 | "HTML TABLE\r\n", 25 | "\r\n", 26 | "\r\n", 27 | "\r\n", 28 | "\r\n", 29 | "\r\n", 30 | "\r\n", 31 | "\r\n", 32 | "\r\n", 33 | "\r\n", 34 | "\r\n", 35 | "\r\n", 36 | "\r\n", 37 | "\r\n", 38 | "\r\n", 39 | "
nameheightmasshair_colorskin_coloreye_colorbirth_yeargender
Luke Skywalker17277blondfairblue19BBYmale
C-3PO16775n/agoldyellow112BBYn/a
R2-D29632n/awhite, bluered33BBYn/a
Darth Vader202136nonewhiteyellow41.9BBYmale
Leia Organa15049brownlightbrown19BBYfemale
Owen Lars178120brown, greylightblue52BBYmale
Beru Whitesun lars16575brownlightblue47BBYfemale
R5-D49732n/awhite, redredunknownn/a
Biggs Darklighter18384blacklightbrown24BBYmale
Obi-Wan Kenobi18277auburn, whitefairblue-gray57BBYmale
\r\n", 40 | "" 41 | ], 42 | "text/plain": [ 43 | "\r\n", 44 | "\r\n", 45 | "\r\n", 46 | "HTML TABLE\r\n", 47 | "\r\n", 48 | "\r\n", 49 | "\r\n", 50 | "\r\n", 51 | "\r\n", 52 | "\r\n", 53 | "\r\n", 54 | "\r\n", 55 | "\r\n", 56 | "\r\n", 57 | "\r\n", 58 | "\r\n", 59 | "\r\n", 60 | "\r\n", 61 | "
nameheightmasshair_colorskin_coloreye_colorbirth_yeargender
Luke Skywalker17277blondfairblue19BBYmale
C-3PO16775n/agoldyellow112BBYn/a
R2-D29632n/awhite, bluered33BBYn/a
Darth Vader202136nonewhiteyellow41.9BBYmale
Leia Organa15049brownlightbrown19BBYfemale
Owen Lars178120brown, greylightblue52BBYmale
Beru Whitesun lars16575brownlightblue47BBYfemale
R5-D49732n/awhite, redredunknownn/a
Biggs Darklighter18384blacklightbrown24BBYmale
Obi-Wan Kenobi18277auburn, whitefairblue-gray57BBYmale
\r\n", 62 | "" 63 | ] 64 | }, 65 | "execution_count": 1, 66 | "metadata": {}, 67 | "output_type": "execute_result" 68 | } 69 | ], 70 | "source": [ 71 | "$swPeople = irm https://swapi.co/api/people\n", 72 | "$People = $swPeople.results \n", 73 | "$People | select name, height, mass, *color, *year, gender | ConvertTo-Html" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 118, 79 | "metadata": { 80 | "inputHidden": false, 81 | "outputHidden": false 82 | }, 83 | "outputs": [ 84 | { 85 | "data": { 86 | "application/javascript": [ 87 | "function appendScript(url, callback) {\n", 88 | " var script = document.createElement('script');\n", 89 | " script.type = 'text/javascript';\n", 90 | " if (script.readyState){ //IE\n", 91 | " script.onreadystatechange = function(){\n", 92 | " if (script.readyState == \"loaded\" ||\n", 93 | " script.readyState == \"complete\"){\n", 94 | " script.onreadystatechange = null;\n", 95 | " callback();\n", 96 | " }\n", 97 | " };\n", 98 | " } else { //Others\n", 99 | " script.onload = function(){\n", 100 | " callback();\n", 101 | " };\n", 102 | " }\n", 103 | " \n", 104 | " script.src = url;\n", 105 | " document.getElementsByTagName(\"head\")[0].appendChild(script);\n", 106 | "}\n", 107 | "appendScript(\"https://cdn.plot.ly/plotly-latest.min.js\", function(){ alert(Plotly); });" 108 | ] 109 | }, 110 | "metadata": {}, 111 | "output_type": "display_data" 112 | } 113 | ], 114 | "source": [ 115 | "function Show-Plotly {\n", 116 | " [CmdletBinding()]\n", 117 | " param($data, $layout)\n", 118 | " if($IsNteract) { \n", 119 | " $plot = [PSCustomObject]@{\n", 120 | " \"application/vnd.plotly.v1+json\" = @{ data = @($data); layout = $layout }\n", 121 | " }\n", 122 | " $plot | Write-Jupyter\n", 123 | " } else {\n", 124 | " $guid = [Guid]::NewGuid().Guid\n", 125 | " $pData = $data | ConvertTo-Json\n", 126 | " $pLayout = $layout | ConvertTo-Json\n", 127 | " Write-Jupyter -MimeType html -Input \"
\"\n", 128 | " Write-Jupyter -MimeType javascript -Input \"Plotly.plot('$guid', $pData, $pLayout)\"\n", 129 | " }\n", 130 | "}\n", 131 | "Write-Jupyter -MimeType javascript -Input @'\n", 132 | "function appendScript(url, callback) {\n", 133 | " var script = document.createElement('script');\n", 134 | " script.type = 'text/javascript';\n", 135 | " if (script.readyState){ //IE\n", 136 | " script.onreadystatechange = function(){\n", 137 | " if (script.readyState == \"loaded\" ||\n", 138 | " script.readyState == \"complete\"){\n", 139 | " script.onreadystatechange = null;\n", 140 | " callback();\n", 141 | " }\n", 142 | " };\n", 143 | " } else { //Others\n", 144 | " script.onload = function(){\n", 145 | " callback();\n", 146 | " };\n", 147 | " }\n", 148 | " \n", 149 | " script.src = url;\n", 150 | " document.getElementsByTagName(\"head\")[0].appendChild(script);\n", 151 | "}\n", 152 | "appendScript(\"https://cdn.plot.ly/plotly-latest.min.js\", function(){ alert(Plotly); });\n", 153 | "'@#>" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 111, 159 | "metadata": { 160 | "inputHidden": false, 161 | "outputHidden": false 162 | }, 163 | "outputs": [ 164 | { 165 | "data": { 166 | "text/html": [ 167 | "
" 168 | ] 169 | }, 170 | "metadata": {}, 171 | "output_type": "display_data" 172 | }, 173 | { 174 | "data": { 175 | "application/javascript": [ 176 | "Plotly.plot('91382c8c-d8b5-4cc3-acfb-7d0edbb39150', [\r\n", 177 | " {\r\n", 178 | " \"y\": [\r\n", 179 | " \"172\",\r\n", 180 | " \"167\",\r\n", 181 | " \"96\",\r\n", 182 | " \"202\",\r\n", 183 | " \"150\",\r\n", 184 | " \"178\",\r\n", 185 | " \"165\",\r\n", 186 | " \"97\",\r\n", 187 | " \"183\",\r\n", 188 | " \"182\"\r\n", 189 | " ],\r\n", 190 | " \"marker\": {\r\n", 191 | " \"color\": \"rgb(55, 83, 109)\"\r\n", 192 | " },\r\n", 193 | " \"name\": \"Height\",\r\n", 194 | " \"type\": \"bar\",\r\n", 195 | " \"x\": [\r\n", 196 | " \"Luke Skywalker\",\r\n", 197 | " \"C-3PO\",\r\n", 198 | " \"R2-D2\",\r\n", 199 | " \"Darth Vader\",\r\n", 200 | " \"Leia Organa\",\r\n", 201 | " \"Owen Lars\",\r\n", 202 | " \"Beru Whitesun lars\",\r\n", 203 | " \"R5-D4\",\r\n", 204 | " \"Biggs Darklighter\",\r\n", 205 | " \"Obi-Wan Kenobi\"\r\n", 206 | " ]\r\n", 207 | " },\r\n", 208 | " {\r\n", 209 | " \"xaxis\": \"x2\",\r\n", 210 | " \"name\": \"Mass\",\r\n", 211 | " \"y\": [\r\n", 212 | " \"77\",\r\n", 213 | " \"75\",\r\n", 214 | " \"32\",\r\n", 215 | " \"136\",\r\n", 216 | " \"49\",\r\n", 217 | " \"120\",\r\n", 218 | " \"75\",\r\n", 219 | " \"32\",\r\n", 220 | " \"84\",\r\n", 221 | " \"77\"\r\n", 222 | " ],\r\n", 223 | " \"type\": \"bar\",\r\n", 224 | " \"marker\": {\r\n", 225 | " \"color\": \"rgb(26,118,225)\"\r\n", 226 | " },\r\n", 227 | " \"x\": [\r\n", 228 | " \"Luke Skywalker\",\r\n", 229 | " \"C-3PO\",\r\n", 230 | " \"R2-D2\",\r\n", 231 | " \"Darth Vader\",\r\n", 232 | " \"Leia Organa\",\r\n", 233 | " \"Owen Lars\",\r\n", 234 | " \"Beru Whitesun lars\",\r\n", 235 | " \"R5-D4\",\r\n", 236 | " \"Biggs Darklighter\",\r\n", 237 | " \"Obi-Wan Kenobi\"\r\n", 238 | " ]\r\n", 239 | " }\r\n", 240 | "], {\r\n", 241 | " \"xaxis2\": {\r\n", 242 | " \"overlaying\": \"x\"\r\n", 243 | " }\r\n", 244 | "})" 245 | ] 246 | }, 247 | "metadata": {}, 248 | "output_type": "display_data" 249 | } 250 | ], 251 | "source": [ 252 | "Show-Plotly @{\n", 253 | " type = \"bar\"\n", 254 | " x = $People.name\n", 255 | " y = $People.height\n", 256 | " name = \"Height\"\n", 257 | "\n", 258 | " marker = @{ color = \"rgb(55, 83, 109)\" }\n", 259 | "},@{\n", 260 | " type = \"bar\"\n", 261 | " x = $People.name\n", 262 | " y = $People.mass\n", 263 | " name = \"Mass\"\n", 264 | " \n", 265 | " marker = @{ color = \"rgb(26,118,225)\" }\n", 266 | " xaxis = \"x2\"\n", 267 | "} @{ \n", 268 | " xaxis2 = @{ \n", 269 | " overlaying = \"x\"\n", 270 | " }\n", 271 | "}\n", 272 | " " 273 | ] 274 | }, 275 | { 276 | "cell_type": "code", 277 | "execution_count": null, 278 | "metadata": { 279 | "collapsed": true 280 | }, 281 | "outputs": [], 282 | "source": [] 283 | } 284 | ], 285 | "metadata": { 286 | "kernel_info": { 287 | "name": "powershell-full" 288 | }, 289 | "kernelspec": { 290 | "display_name": "PowerShell (Full)", 291 | "language": "PowerShell", 292 | "name": "powershell-full" 293 | }, 294 | "language_info": { 295 | "codemirror_mode": "powershell", 296 | "file_extension": ".ps1", 297 | "mimetype": "text/powershell", 298 | "name": "PowerShell", 299 | "nbconvert_exporter": null, 300 | "pygments_lexer": "powershell", 301 | "version": "6.0" 302 | }, 303 | "nteract": { 304 | "version": "0.1.0" 305 | } 306 | }, 307 | "nbformat": 4, 308 | "nbformat_minor": 4 309 | } 310 | -------------------------------------------------------------------------------- /LiterateDevOps.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "F.A.Q. | Answer\n", 12 | "-------|---------\n", 13 | "Who am I? | Joel \"Jaykul\" Bennett\n", 14 | "What is this? | PowerShell Jupyter Kernel\n", 15 | "Where is it? | [jaykul/Jupyter-PowerShell](https://github.com/jaykul/Jupyter-PowerShell)\n" 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": { 21 | "slideshow": { 22 | "slide_type": "slide" 23 | } 24 | }, 25 | "source": [ 26 | "# Literate Programming\n", 27 | "\n", 28 | "> I believe that the time is ripe for significantly better documentation of programs, and that we can best achieve this by considering programs to be works of literature. \n", 29 | "- Donald Knuth's 1984 book _Literate Programming_\n", 30 | "\n", 31 | "Of course, we gave that the old college try and here we are 34 years later and we're still struggling with poor documentation! In the DevOps world, good documentation of prcoesses is more valuable than ever, so what are we to do?\n", 32 | "\n", 33 | "## How can I motivate you to document better?\n", 34 | "\n", 35 | "I propose some tools which allow you to not only combine your documentation with your code, but with the logs of the execution, reports, etc. If I can't get your attention one way, maybe another will work. " 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 2, 41 | "metadata": { 42 | "slideshow": { 43 | "slide_type": "slide" 44 | } 45 | }, 46 | "outputs": [ 47 | { 48 | "data": { 49 | "text/plain": [ 50 | "Directory: /home/jovyan/work\n", 51 | "\n", 52 | "\n", 53 | "Mode LastWriteTime Length Name \n", 54 | "---- ------------- ------ ---- \n", 55 | "--r--- 4/21/18 10:46 PM 33800 LiterateDevOps.ipynb \n", 56 | "--r--- 4/20/18 6:34 AM 1546 Dockerfile \n", 57 | "--r--- 4/20/18 4:19 AM 2974 build.ps1 \n", 58 | "--r--- 4/6/18 3:20 AM 4354 LiterateDevOps.md \n", 59 | "--r--- 4/6/18 3:20 AM 170560 ReadMe.ipynb \n", 60 | "--r--- 4/6/18 3:09 AM 12423 nteract - plotly.ipynb \n", 61 | "--r--- 3/4/18 2:39 AM 2302 ReadMe.md \n", 62 | "--r--- 2/20/18 6:44 AM 4682 jupyter-powershell.nuspec \n", 63 | "--r--- 8/4/17 3:19 AM 8712 Release.ipynb \n", 64 | "--r--- 6/24/17 7:07 AM 1140 LICENSE.md" 65 | ] 66 | }, 67 | "execution_count": 2, 68 | "metadata": {}, 69 | "output_type": "execute_result" 70 | } 71 | ], 72 | "source": [ 73 | "$Files = Get-ChildItem -File\n", 74 | "$Files | Sort-Object LastWriteTime -Descending" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": {}, 80 | "source": [ 81 | "The PowerShell kernel also includes a `Write-Jupyter` command that can directly output HTML, javascript, images, etc.\n", 82 | "\n", 83 | "For example: if you take your files and convert them to an HTML table, you can output that table inline. For that to work, you really want to use `ConvertTo-Html -Fragment` to get just the table as output.\n", 84 | "\n", 85 | "of course, we really want that to happen automatically. I'll have to add some `profile` support at some point to make that happen, but in the meantime, you can put something like this in a cell near the top, and make it work from then on:" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": 19, 91 | "metadata": {}, 92 | "outputs": [], 93 | "source": [ 94 | "$PSDefaultParameterValues[\"ConvertTo-Html:Fragment\"] = $true" 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "metadata": {}, 100 | "source": [ 101 | "Incidentally, there's one other problem that I just noticed. Currently, `Write-Jupyter` outputs each item as it comes in, and wraps them in a tag, so to get a table to output properly, you need one output string. ConvertTo-Html outputs one `` at a time, so we need to use `Out-String` or `-join` them all together:" 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": 21, 107 | "metadata": {}, 108 | "outputs": [ 109 | { 110 | "data": { 111 | "text/html": [ 112 | "\n", 113 | "\n", 114 | "\n", 115 | "\n", 116 | "\n", 117 | "\n", 118 | "\n", 119 | "\n", 120 | "\n", 121 | "\n", 122 | "\n", 123 | "\n", 124 | "\n", 125 | "
ModeLastWriteTimeNameLength
--r---4/20/18 4:19:33 AMbuild.ps12974
--r---4/20/18 6:34:12 AMDockerfile1546
--r---2/20/18 6:44:41 AMjupyter-powershell.nuspec4682
--r---6/24/17 7:07:27 AMLICENSE.md1140
--r---4/21/18 10:46:19 PMLiterateDevOps.ipynb33800
--r---4/6/18 3:20:07 AMLiterateDevOps.md4354
--r---4/6/18 3:09:39 AMnteract - plotly.ipynb12423
--r---4/6/18 3:20:07 AMReadMe.ipynb170560
--r---3/4/18 2:39:59 AMReadMe.md2302
--r---8/4/17 3:19:47 AMRelease.ipynb8712
\n" 126 | ] 127 | }, 128 | "metadata": {}, 129 | "output_type": "display_data" 130 | } 131 | ], 132 | "source": [ 133 | "$Files | ConvertTo-Html Mode, LastWriteTime, Name, Length | Out-String | Write-Jupyter -MimeType html" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "metadata": {}, 139 | "source": [ 140 | "Now if you want to get clever, you can dump a little javascript in, to make the table sortable. run this line, and then use your mouse on the headers of the table:" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": 22, 146 | "metadata": {}, 147 | "outputs": [ 148 | { 149 | "data": { 150 | "text/html": [ 151 | "" 152 | ] 153 | }, 154 | "metadata": {}, 155 | "output_type": "display_data" 156 | } 157 | ], 158 | "source": [ 159 | "'' | Write-Jupyter -MimeType html" 160 | ] 161 | }, 162 | { 163 | "cell_type": "markdown", 164 | "metadata": {}, 165 | "source": [ 166 | "Of course, a better trick would be to make that happen automatically. \n", 167 | "\n", 168 | "I'll see what I can do about that in the future, because this is starting to feel like a lot of modifications for `ConvertTo-Html` -- I'd love some feedback on this. In the meantime, you can add that script to the defaults for `ConvertTo-Html` like what we did with fragment:" 169 | ] 170 | }, 171 | { 172 | "cell_type": "code", 173 | "execution_count": 24, 174 | "metadata": {}, 175 | "outputs": [], 176 | "source": [ 177 | "$PSDefaultParameterValues[\"ConvertTo-Html:Fragment\"] = $true\n", 178 | "$PSDefaultParameterValues[\"ConvertTo-Html:PostContent\"] = ''" 179 | ] 180 | }, 181 | { 182 | "cell_type": "markdown", 183 | "metadata": {}, 184 | "source": [ 185 | "Now you can get a sortable table instantly, by just running ConvertTo-Html.\n", 186 | "\n", 187 | "When I tried this as an example, I remembered another reason why we're going to want a better ConvertTo-Html. It doesn't know anything about which columns should be visible, and it doesn't handle properties that are collections:" 188 | ] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "execution_count": 29, 193 | "metadata": {}, 194 | "outputs": [ 195 | { 196 | "data": { 197 | "text/html": [ 198 | "\n", 199 | "\n", 200 | "\n", 201 | "\n", 202 | "\n", 203 | "
LogPipelineExecutionDetailsNamePathImplementingAssemblyDefinitionDescriptionGuidHelpInfoUriModuleBasePrivateDataTagsProjectUriIconUriLicenseUriReleaseNotesRepositorySourceLocationVersionModuleTypeAuthorAccessModeClrVersionCompanyNameCopyrightDotNetFrameworkVersionExportedFunctionsPrefixExportedCmdletsExportedCommandsFileListCompatiblePSEditionsModuleListNestedModulesPowerShellHostNamePowerShellHostVersionPowerShellVersionProcessorArchitectureScriptsRequiredAssembliesRequiredModulesRootModuleExportedVariablesExportedAliasesExportedWorkflowsExportedDscResourcesSessionStateOnRemoveExportedFormatFilesExportedTypeFiles
FalseMicrosoft.PowerShell.Management/usr/src/jupyter-powershell/Modules/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1eefcb906-b326-4e99-9f54-8b4bb6ef3c6dhttps://go.microsoft.com/fwlink/?linkid=855958/usr/src/jupyter-powershellSystem.Collections.Generic.List`1[System.String]3.1.0.0ManifestPowerShellReadWriteMicrosoft CorporationCopyright (c) Microsoft Corporation. All rights reserved.System.Collections.Generic.Dictionary`2[System.String,System.Management.Automation.FunctionInfo]System.Collections.Generic.Dictionary`2[System.String,System.Management.Automation.CmdletInfo]System.Collections.Generic.Dictionary`2[System.String,System.Management.Automation.CommandInfo]System.Collections.Generic.List`1[System.String]System.Collections.Generic.List`1[System.String]System.Collections.ObjectModel.Collection`1[System.Object]System.Collections.ObjectModel.ReadOnlyCollection`1[System.Management.Automation.PSModuleInfo]3.0NoneSystem.Collections.Generic.List`1[System.String]System.Collections.ObjectModel.Collection`1[System.String]System.Collections.ObjectModel.ReadOnlyCollection`1[System.Management.Automation.PSModuleInfo]System.Collections.Generic.Dictionary`2[System.String,System.Management.Automation.PSVariable]System.Collections.Generic.Dictionary`2[System.String,System.Management.Automation.AliasInfo]System.Collections.Generic.Dictionary`2[System.String,System.Management.Automation.FunctionInfo]System.Collections.ObjectModel.ReadOnlyCollection`1[System.String]System.Management.Automation.SessionStateSystem.Collections.ObjectModel.ReadOnlyCollection`1[System.String]System.Collections.ObjectModel.ReadOnlyCollection`1[System.String]
FalseMicrosoft.PowerShell.Utility/usr/src/jupyter-powershell/Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd11da87e53-152b-403e-98dc-74d7b4d63d59https://go.microsoft.com/fwlink/?linkid=855960/usr/src/jupyter-powershellSystem.Collections.Generic.List`1[System.String]3.1.0.0ManifestPowerShellReadWriteMicrosoft CorporationCopyright (c) Microsoft Corporation. All rights reserved.System.Collections.Generic.Dictionary`2[System.String,System.Management.Automation.FunctionInfo]System.Collections.Generic.Dictionary`2[System.String,System.Management.Automation.CmdletInfo]System.Collections.Generic.Dictionary`2[System.String,System.Management.Automation.CommandInfo]System.Collections.Generic.List`1[System.String]System.Collections.Generic.List`1[System.String]System.Collections.ObjectModel.Collection`1[System.Object]System.Collections.ObjectModel.ReadOnlyCollection`1[System.Management.Automation.PSModuleInfo]3.0NoneSystem.Collections.Generic.List`1[System.String]System.Collections.ObjectModel.Collection`1[System.String]System.Collections.ObjectModel.ReadOnlyCollection`1[System.Management.Automation.PSModuleInfo]System.Collections.Generic.Dictionary`2[System.String,System.Management.Automation.PSVariable]System.Collections.Generic.Dictionary`2[System.String,System.Management.Automation.AliasInfo]System.Collections.Generic.Dictionary`2[System.String,System.Management.Automation.FunctionInfo]System.Collections.ObjectModel.ReadOnlyCollection`1[System.String]System.Management.Automation.SessionStateSystem.Collections.ObjectModel.ReadOnlyCollection`1[System.String]System.Collections.ObjectModel.ReadOnlyCollection`1[System.String]
\n", 204 | "\n" 205 | ] 206 | }, 207 | "metadata": {}, 208 | "output_type": "display_data" 209 | } 210 | ], 211 | "source": [ 212 | "Get-Module | ConvertTo-Html | Out-String | Write-Jupyter -MimeType html" 213 | ] 214 | }, 215 | { 216 | "cell_type": "code", 217 | "execution_count": 30, 218 | "metadata": {}, 219 | "outputs": [ 220 | { 221 | "data": { 222 | "image/png": "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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" 223 | }, 224 | "metadata": { 225 | "image/png": { 226 | "width": 32 227 | } 228 | }, 229 | "output_type": "display_data" 230 | }, 231 | { 232 | "data": { 233 | "image/png": "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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" 234 | }, 235 | "metadata": { 236 | "image/png": { 237 | "width": 64 238 | } 239 | }, 240 | "output_type": "display_data" 241 | } 242 | ], 243 | "source": [ 244 | "$imageUrl = 'https://upload.wikimedia.org/wikipedia/commons/2/2f/PowerShell_5.0_icon.png'\n", 245 | "$ImageData = @{ \"png\" = (Invoke-WebRequest $imageUrl -UseBasicParsing).RawContentStream.GetBuffer() }\n", 246 | "# $ImageData\n", 247 | "\n", 248 | "Write-Jupyter -InputObject $ImageData -Metadata @{ \"image/png\" = @{ 'width' = 32 } }\n", 249 | "Write-Jupyter -InputObject $ImageData -Metadata @{ \"image/png\" = @{ 'width' = 64 } }" 250 | ] 251 | } 252 | ], 253 | "metadata": { 254 | "kernelspec": { 255 | "display_name": "PowerShell", 256 | "language": "PowerShell", 257 | "name": "powershell" 258 | }, 259 | "language_info": { 260 | "codemirror_mode": "powershell", 261 | "file_extension": ".ps1", 262 | "mimetype": "text/powershell", 263 | "name": "PowerShell", 264 | "nbconvert_exporter": null, 265 | "pygments_lexer": "powershell", 266 | "version": "5.0" 267 | } 268 | }, 269 | "nbformat": 4, 270 | "nbformat_minor": 2 271 | } 272 | --------------------------------------------------------------------------------