Giter Site home page Giter Site logo

Comments (19)

nickcox avatar nickcox commented on May 19, 2024 3

For the issue around quoting rules, you can access the invoked command with $MyInvocation. This seems to work for me so far:

function Invoke-Gsudo {
  gsudo.exe ($MyInvocation.Line -replace "^$($MyInvocation.InvocationName)\s+" -replace '"','""')
}

❯ set-alias sudo Invoke-Gsudo
❯ sudo echo "abc def"
abc def

❯ _

from gsudo.

oising avatar oising commented on May 19, 2024 2

So one thing you could do is to create a proxy command for Invoke-Expression to add an -Elevate parameter. Inventing new verbs (elevate) is frowned upon in the powershell world. You can have powershell automagically generate a wrapper function and pipe it to the clipboard like so:

[System.Management.Automation.ProxyCommand]::Create((gcm invoke-expression)) | clip

You end up with a function body (which you would wrap in a function Invoke-Expression { ... } block.) Functions have precedence over native Cmdlets.

[CmdletBinding(HelpUri='https://go.microsoft.com/fwlink/?LinkID=2097030')]
param(
    [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
    [string]
    ${Command})

begin
{
    try {
        $outBuffer = $null
        if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
        {
            $PSBoundParameters['OutBuffer'] = 1
        }

        $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Invoke-Expression', [System.Management.Automation.CommandTypes]::Cmdlet)
        $scriptCmd = {& $wrappedCmd @PSBoundParameters }

        $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
        $steppablePipeline.Begin($PSCmdlet)
    } catch {
        throw
    }
}

process
{
    try {
        $steppablePipeline.Process($_)
    } catch {
        throw
    }
}

end
{
    try {
        $steppablePipeline.End()
    } catch {
        throw
    }
}
<#

.ForwardHelpTargetName Microsoft.PowerShell.Utility\Invoke-Expression
.ForwardHelpCategory Cmdlet

#>

You would then add a new switch parameter to the param block, [switch]$Elevate. This is a reasonable way to add new functionality to an out of the box cmdlet. But given that you're pushing the work to gsudo, you'd rip out the steppable pipeline stuff and replace with your own marshalling script. Just an idea.

from gsudo.

oising avatar oising commented on May 19, 2024 2

btw, powershell.exe and pwsh.exe can automatically deserialize psserialized objects from a native command (e.g. cmd batch, exe or other out of proc process) if the stdin stream is prefixed with a special marker sequence. Check this out:

Create a batch file foo.cmd (escaping < and > with ^)

@echo #^< CLIXML
@echo ^<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"^>^<I32^>42^</I32^>^</Objs^>

Now, pipe this command to foreach %

.\foo.cmd | % { $_ }
42

Magic!

So if your out of proc elevation serializes the output, then we could pipe an inline elevated expression to another cmdlet/function without having to insert ugly deserialization code.

from gsudo.

oising avatar oising commented on May 19, 2024 2

Oh this could also help - you can get powershell to serialize AND add the magic marker itself like this:

pwsh -noprofile -outputformat xml -command { gi c:\windows }
#< CLIXML
<Objs ... >

Example rehydrating in another pwsh process:

pwsh -noprofile -outputformat xml -command { gi c:\windows } | pwsh -noprofile -command { $input | % { $_ } }
Directory: C:\

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          2020-06-15  2:58 PM                Windows

from gsudo.

oising avatar oising commented on May 19, 2024 1

I'd be happy to contribute, but that said, I haven't played with your magical powershell elevation. I suspect it secretly sends the work to an out of process instance of powershell. Am I right? Trying to pretend that a scriptblock is in the same lexical scope as the caller might lead to all kinds of weird situations as the variables wouldn't exist in that other runspace's sessionstate. Or are you doing something sneakier?

from gsudo.

gerardog avatar gerardog commented on May 19, 2024 1

e.g. Elevate-Command.ps1

$a = 10
Elevate-Command -ScriptBlock { $b = $using:a ; $b = $b+1 ; $b }

returns 11!

from gsudo.

majkinetor avatar majkinetor commented on May 19, 2024 1

I bet people would throw me stones if It doesnt respect the verb-noun form.

The correct way is to name it in verb-noun (Invoke-Gsudo) manner and set alias to whatever (gsudo).

from gsudo.

mattcargile avatar mattcargile commented on May 19, 2024 1

The -EncodedCommand parameter seems helpful here too. It would save much of the quoting headaches.

I use it in this function I stole from Lee Holmes in order to run psexec , sysinternals tool.

Here is the applicable piece of code which appears in the help for pwsh and powershell , i think.

## $expression -eq [scriptblock]
## Convert the command into an encoded command for PowerShell
$commandBytes = [System.Text.Encoding]::Unicode.GetBytes($expression)
$encodedCommand = [Convert]::ToBase64String($commandBytes)
$commandLine += "-EncodedCommand $encodedCommand"

from gsudo.

gerardog avatar gerardog commented on May 19, 2024 1

Matt! You anticipated me. I will post what I had in draft and then address your comments:

Hi again!
Sorry for not having the time to focus on gsudo as much as I wanted lately. Let's try to move this one forward a little bit.

There is an experimental Invoke-gsudo.ps1 that I want to open for early testing. Put it in your path, preferable in gsudo's folder (get-command gsudo.exe | % { (get-item $_.Source ).Directory.FullName })

This is a glimpse of current status:

.DESCRIPTION
Serializes a scriptblock and executes it in an elevated powershell. 
The ScriptBlock runs in a different process, so it can´t read/write variables from the invoking scope.
If you reference a variable in a scriptblock using the `$using:variableName` it will be replaced with it´s serialized value.
The elevated command can accept input from the pipeline with $Input. It will be serialized, so size matters.
The script result is serialized, sent back to the non-elevated instance, and returned.
Optionally you can check for "$LastExitCode -eq 999" to find out if gsudo failed to elevate (UAC popup cancelled) 

.EXAMPLE
PS> Get-Process notepad | Invoke-gsudo { Stop-Process }

PS> Invoke-gsudo { return Get-Content 'C:\My Secret Folder\My Secret.txt'}

PS> $a=1; $b = Invoke-gsudo { $using:a+10 }; Write-Host "Sum returned: $b";

Sum returned: 11

Problems/Challenges

  • Steppable input is supported, but serialized all at once. (It's not streamed one-by-one). Large input will probably cause issues.
    I don't think this is such a big issue at this point since the user could move the elevation boundary to minimize the serialization and performance hit.

  • Commands return issue: EDIT: FIXED
    As of now, Invoke-gsudo is requiring an extra 'return' statement on some cases. For example:

    • PS C:\> Invoke-Command { Get-Content C:\Test.txt } for reference, Invoke-Command works (returns file content).
    • PS C:\> Invoke-gsudo { Get-Content C:\Test.txt } doesn't work, returns nothing.
    • PS C:\> Invoke-gsudo { return Get-Content C:\Test.txt } works.
    • PS C:\> Invoke-gsudo { Get-Content C:\Test.txt ; 1 } works, returns the content and a 1.
  • Exception handling:

I'm not able to distinguish between Terminating (Invoke-gsudo { throw}) and non-terminating errors ( Invoke-gsudo { Write-Error 1; Write-Error 2;}) here
and I think the ErrorAction needs to be Stop for the formers so they trigger catch blocks.

  • Only works in TokenMode, at least for now.
    Basically dont change gsudo settings like: SecurityEnforceUacIsolation or ForceXXXConsole

Awaiting feedback mode: ON

from gsudo.

gerardog avatar gerardog commented on May 19, 2024 1

Matt, $inputObject allows to do Get-Process notepad | Invoke-gsudo { Stop-Process }. You will see it as $Input inside the ScriptBlock since it is an automatic variable generated by Pwsh Invoke-Command.
Great catch it's causing the 'return' issue. Pushing a fix right now.

from gsudo.

gerardog avatar gerardog commented on May 19, 2024 1

I'm not able to distinguish between Terminating (Invoke-gsudo { throw}) and non-terminating errors ( Invoke-gsudo { Write-Error 1; Write-Error 2;}) here
and I think the ErrorAction needs to be Stop for the formers so they trigger catch blocks.

TIL: Apparently, nobody can. See PowerShell/PowerShell#3158

I would think we would let the users determine the ErrorAction flow within the [scriptblock] passed into Invoke-gsudo.ps1? I would assume the aforementioned line should avoid declaring an ErrorAction and let the users $ErrorActionPreference come into play.

I agree. I removed specifying the fixed ErrorAction you are referring to.

  • $Error.Exception.WasThrownFromThrowStatement

Thanks, that really helped. Now I do try/catch on the scriptblock to detect the terminating errors, and force throw $_ to ensure WasThrownFromThrowStatement is true. Works 99% 🤷‍♂️.

Now, should I honor Invoke-gsudo -ErrorAction parameter? Invoke-Command does not. No difference between:
- Invoke-Command { "/", "/nofound", "/" | Get-Item } -ErrorAction Stop
- Invoke-Command { "/", "/nofound", "/" | Get-Item }

It felt harmless to take the -ErrorAction param (IF specified) and setting that as $ErrorActionPreference in the elevated instance. Opinions?

If omitted the invokers ErrorActionPreference is forwarded to the elevated instance.

I may be confused with the use cases for the error handling.

Me too. I'm learning as I code here. Found this great recap on error handling on PS: See MicrosoftDocs/PowerShell-Docs#1583
I'd mimic Invoke-Command assuming it's doing the proper thing, or what the user would expect, plus that is easier to communicate.

In a nutshell, keep the same exception type, keep the same stream. For example:

  • Non-terminating errors:
    • Should not terminate the pipeline execution.
    • Capturable with -ErrVarible or 2>&1
    • E.g. Invoke-gsudo { "\", "notExisting" | Get-Item } } -errVariable failed
  • Terminating errors
    • Capturable with try/catch
    • try { Invoke-Gsudo { throw; } } catch {"Catched: $_"}

Now, I still have problems between Script-terminating errors and Statement-terminating errors.
Apparently surrounding the script in a try/catch turns a Statement-terminating error into a Script-terminating error (in this context, actually into a scriptblock-terminating).

For example, Invoke-Command behaves differently surrounded by try/catch:

PS C:\Users\gerar> Invoke-Command { Some-InvalidCmdLet ; 10 }
Some-InvalidCmdLet: The term 'Some-InvalidCmdLet' is not recognized as a name of a cmdlet, function, script file, or executable program.
10

PS C:\Users\gerar> try { Invoke-Command { Some-InvalidCmdLet ; 10 } } catch { "Catched: $_" }
Catched: The term 'Some-InvalidCmdLet' is not recognized as a name of a cmdlet, function, script file, or executable program.

The later stops execution at Some-InvalidCmdLet and never returns 10.
The problem here is that I use try catch to detect termining errors.

So, is it correct to conclude that all statement-terminating errors will turn into script-terminating?

I was playing with the below.

Invoke-gsudo.ps1 { write-error 1; write-error 2; 2 }

I would expect it to return

Invoke-gsudo.ps1: 1
Invoke-gsudo.ps1: 2
2

Currently it is returning

2
Invoke-gsudo.ps1: 1

I guess it is because of the redirection ( 2>&1 ) changing the order of the errors and then the ErrorAction on the aforementioned line.

Yes, the redirection changes the output order. No workaround AFAIK.
Also, the second exception was lost. Should be fixed now.

Here are a few test cases. Ideally each pair of lines should behave the same, but they don't. (Invoke-Command vs Invoke-gsudo).

Proper PS tests may exists in the future, for now a comment will do.

try { invoke-gsudo { 0/0 } } catch { "catched: $_" }
try { Invoke-Command { 0/0 } } catch { "catched: $_" }

Invoke-gsudo { 0/0;0/0 } # fail: only 1 exception, caused by try/catch
Invoke-Command { 0/0;0/0 } 

try { invoke-gsudo   { "asd" | ConvertFrom-Json } } catch { "catched: $_" } 
try { Invoke-Command { "asd" | ConvertFrom-Json } } catch { "catched: $_" } # Weird, why catched? Oh.. PowerShell/PowerShell#2860

try { Invoke-Command { throw } } catch { "catched: $_" }
try { invoke-gsudo   { throw } } catch { "catched: $_" }

try { invoke-Command { "notfound" | Get-Item } } catch { "catched: $_" }
try { invoke-gsudo   { "notfound" | Get-Item } } catch { "catched: $_" }

try { invoke-Command { "notfound" | Get-Item -ErrorAction Stop } } catch { "catched: $_" }
try { invoke-gsudo   { "notfound" | Get-Item -ErrorAction Stop } } catch { "catched: $_" }

try { invoke-Command { ".", "notfound" | Get-Item -ErrorAction Stop } } catch { "catched: $_" }
try { invoke-gsudo   { ".", "notfound" | Get-Item -ErrorAction Stop } } catch { "catched: $_" }

#weird fail. Also, run as admin
try { invoke-command { Get-Service "netlogon" | Suspend-Service -ErrorAction Stop }  } catch { "catched: $_" }
try { invoke-gsudo { Get-Service "netlogon" | Suspend-Service -ErrorAction Stop }  } catch { "catched: $_" }

try { invoke-Command { Get-InvalidCmdLet ; 1 } } catch { "catched: $_" }
try { invoke-gsudo   { Get-InvalidCmdLet ; 1 } } catch { "catched: $_" }

try { invoke-gsudo { [int]::Parse('foo') }  } catch { "e $_"  }
try { invoke-command { [int]::Parse('foo') }  } catch { "e $_"  }

I've just pushed a new version. Feedback welcomed!

from gsudo.

gerardog avatar gerardog commented on May 19, 2024 1

Uploaded tests, and bugfixing.

PS C:\git\gsudo> Invoke-Pester -Output Detailed
Pester v5.3.1

Starting discovery in 2 files.
Discovery found 16 tests in 205ms.
Running tests.

Running tests from 'C:\git\gsudo\src\gsudo.extras\gsudo.Tests.ps1'
Describing PS Gsudo (v7.2.1)
  [+] It serializes return values as string. 2.33s (2.3s|38ms)
  [+] When invoked as gsudo !!, It elevates the last command executed 452ms (451ms|1ms)

Running tests from 'C:\git\gsudo\src\gsudo.extras\Invoke-gsudo.Tests.ps1'
Describing PS Invoke-Gsudo (v7.2.1)
  [+] It serializes return values maintaining its type 569ms (568ms|1ms)
  [+] It serializes return values mantaining its properties. 503ms (502ms|1ms)
  [+] It returns an array of values mantaining its properties. 524ms (523ms|1ms)
  [+] It accepts objects from the pipeline. 482ms (480ms|2ms)
  [+] It throws when Error thrown 622ms (621ms|1ms)
  [+] It throws with expression runtime errors 1.14s (1.14s|1ms)
  [+] It throws with .Net Exceptions 577ms (577ms|1ms)
  [+] It throws when ErrorAction = Stop 570ms (570ms|1ms)
  [+] It throws when ErrorActionPreference = Stop 550ms (550ms|0ms)
  [+] It forwards ErrorActionPreference 'Stop' to the elevated instance 527ms (525ms|2ms)
  [+] It forwards ErrorActionPreference 'Continue' to the elevated instance 498ms (498ms|1ms)
  [+] It forwards ErrorActionPreference 'Ignore' to the elevated instance 494ms (493ms|1ms)
  [+] It doesn't throw when ErrorActionPreference = Continue 1.23s (1.23s|1ms)
  [+] It doesn't throw with '-ErrorAction Continue-' 2.69s (2.69s|1ms)
Tests completed in 49.29s
Tests Passed: 16, Failed: 0, Skipped: 0 NotRun: 0

Also on the build server: CI test results with inconvenient ordering.

And last, but not least: I added a gsudo PowerShell Module: (Add Import-Module pathto/gsudoModule.psm1 to your $PROFILE) which adds support for gsudo !! too. (As requested in #44)

from gsudo.

gerardog avatar gerardog commented on May 19, 2024 1

Released in v1.1.0

from gsudo.

oising avatar oising commented on May 19, 2024

There isn't really a good reason to write a native (i.e. C# compiled) wrapper cmdlet as far as I can see? If you want to marshal parameters using powershell's binder to gsudo's, a wrapper function would do the trick. Or perhaps you're not aware that cmdlet infers compiled code, and function infers script?

from gsudo.

gerardog avatar gerardog commented on May 19, 2024

Oh, I didn't mean a native c# cmdlet, but a function script.
My intent is just to add one Invoke-gsudo.ps1 file side by side with gsudo.exe, both in the PATH.
Then you could Invoke-gsudo -ScriptBlock { Do-ElevatedStuff } or whatever syntax is more welcomed for PS developers than the current (quote-escaping-hell) syntax...

from gsudo.

gerardog avatar gerardog commented on May 19, 2024

That's right. gsudo.exe does very little to support elevating PS commands. The trick is that it is the user's responsibility to generate a string with a valid PS command (hence the quoting-hell) and then, when gsudo detects it's being called from PS, it elevates the same PowerShell.exe with -c {command}.

This can be seen with the --debug parameter:

PS $ gsudo "(Get-FileHash \""$file\"" -Algorithm $algorithm).Hash"

... executes gsudo "(Get-FileHash \"C:\test\test.txt\" -Algorithm MD5).Hash" which then calls pwsh again:

Debug: Command to run: "C:\Users\ggrignoli\scoop\apps\pwsh\current\pwsh.exe" -NoLogo -NoProfile -Command "(Get-FileHash \"C:\test\test.txt\" -Algorithm MD5).Hash"
(...)
79F1F6A36DBEBF6888E9E1717766F5A6

In that model, the user should be aware that there is no object marshalling, only strings in and out. In the example you can capture the result because .Hash returns a simple string, but good luck if it were a complex object.

So, what's next? The point of this issue is that I am open to suggestions and gather info on how far we can go.

Trying to pretend that a scriptblock is in the same lexical scope

It should be clear from the ground up that its not. There is a process-hop between elevated an unelevated instances so there will be serializing and deserializing of objects.

What is PS-Remoting doing with both local and remote scopes? Can we mimic that?

Take a look at this, it serializes a scriptblock while allowing the $using:LocalVariable syntax: it substitutes from $using:variableA to: $(Deserialize('serialized value of variableA')).

i.e. Write-Host $a becomes
Write-Host $([System.Management.Automation.PSSerializer]::Deserialize('&lt;Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell /2004/04"&gt;_x000D__x000A_ &lt;S&gt;HELLO&lt;/S&gt;_x000D__x000A_&lt;/Objs&gt;'))

AFAIK this would allow this syntax: Elevate-Command -ScriptBlock { $using:localVar + $remoteVar }, unless I am missing something.

Serialization will produce huge strings, but we can use pwsh -c - and then send the scriptblock via StdIn.
The output should be serialized before going to StdOut, then deserialized as well.

from gsudo.

gerardog avatar gerardog commented on May 19, 2024

New version (same link), supports -ArgumentList and pipe input is received via $Input.

$local = "outside scope"

"TestString" | Elevate-Command -ScriptBlock {
    $local = "inner scope"
    Write-Host "Am I elevated? $([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match 'S-1-5-32-544'))"
    Write-Host "Inner scope value: $local"
    Write-Host "Outer scope value: $using:local"
    Write-Host "Received Args: $args"
    Write-Host "Pipeline Input: $Input"
} -ArgumentList 1, 2, 3

returns

Am I elevated? True
Inner scope value: inner scope
Outer scope value: outside scope
Received Args: 1 2 3
Pipeline Input: TestString

I've tried to make Elevate-Command equivalent to Invoke-Command, except that the latter does not supports $using: syntax on localhost.
Does it sound practical? What could be improved? Opinions?

Now I've been looking at Ìnvoke-Expression... I am the exploring the idea of providing an Elevate-Expression function. I guess it will look similar to gsudo right now, but with a few differences:

  • Quoting rules would be exactly PS rules, not subtle differences like " => \""
  • Support $using: which will work by serializing the object using PSSerializer.

Any feedback at this early stage is highly welcome.

from gsudo.

mattcargile avatar mattcargile commented on May 19, 2024

I was playing around with the new build. I couldn't understand the piped $InputObject piece. When I removed it, code like the below worked.

Invoke-gsudo -ScriptBlock { Get-Content .\readline.ps1 } -NoElevate

try { ($InputObject | Invoke-Command $sb -ArgumentList $argumentList ) } catch { Write-Output $_ }

Some of this may feed into the $Input variable not being defined. Should $Input be $InputObject? I see now, I didn't know about the $Input automatic variable.

$InputArray = $Input

When I changed the above line to $InputObject , I received the below error.

ErrorRecord                 : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and
                              its properties do not match any of the parameters that take pipeline input.
WasThrownFromThrowStatement : False
TargetSite                  : System.Collections.ObjectModel.Collection`1[System.Management.Automation.PSObject] Invoke(System.Collections.IEnumerable)
Message                     : The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: The input object
                              cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties
                              do not match any of the parameters that take pipeline input.
Data                        : {System.Management.Automation.Interpreter.InterpretedFrameInfo}
InnerException              :
HelpLink                    :
Source                      : System.Management.Automation
HResult                     : -2146233087
StackTrace                  :    at System.Management.Automation.Runspaces.PipelineBase.Invoke(IEnumerable input)
                                 at Microsoft.PowerShell.Executor.ExecuteCommandHelper(Pipeline tempPipeline, Exception& exceptionThrown, ExecutionOptions options)

from gsudo.

mattcargile avatar mattcargile commented on May 19, 2024

I'm not able to distinguish between Terminating (Invoke-gsudo { throw}) and non-terminating errors ( Invoke-gsudo { Write-Error 1; Write-Error 2;}) here
and I think the ErrorAction needs to be Stop for the formers so they trigger catch blocks.

I would think we would let the users determine the ErrorAction flow within the [scriptblock] passed into Invoke-gsudo.ps1? I would assume the aforementioned line should avoid declaring an ErrorAction and let the users $ErrorActionPreference come into play.

As far as telling the difference between the errors, I was reading this and this. Maybe we can use one of these:

  • $Error.CategoryInfo.Category
  • $Error.Exception.WasThrownFromThrowStatement

Category appears to always be OperationStopped due to a System.Management.Automation.PipelineStoppedException being called by ThrowTerminatingError(ErrorRecord). This is opposed to a non-terminating error which calls System.Management.Automation.Cmdlet.WriteError. Not sure if we need to tell a difference between Write-Error 1 and Write-Error 1 -ErrorAction Stop?

I may be confused with the use cases for the error handling. I was playing with the below.

Invoke-gsudo.ps1 { write-error 1; write-error 2; 2 }

I would expect it to return

Invoke-gsudo.ps1: 1
Invoke-gsudo.ps1: 2
2

Currently it is returning

2
Invoke-gsudo.ps1: 1

I guess it is because of the redirection ( 2>&1 ) changing the order of the errors and then the ErrorAction on the aforementioned line.

As another example, I was playing with the below which does work as I would expect.

Invoke-gsudo.ps1 { throw 'hello world'; 2 }

from gsudo.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.