toenuff / flancy

A micro web framework for Windows PowerShell
MIT License
189 stars 21 forks source link

Can't run latest Flancy as Job anymore #33

Open beatcracker opened 8 years ago

beatcracker commented 8 years ago

Repro script:

Start-Job -Name 'Flancy' -ScriptBlock {
    Import-Module -Name 'Flancy' -ErrorAction Stop
    New-Flancy -WebSchema @{
        Method = 'Get'
        Path= '/'
        Script = {'Hello, world!'}
    }
}

Wait-Job -Name 'Flancy' | Receive-Job

Output:

PS > .\Flancy-AsJob.ps1

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
2      Flancy          BackgroundJob   Running       True            localhost            ...

Exception calling ".ctor" with "1" argument(s): "Unable to resolve type: Nancy.ViewEngines.ViewEngineApplicationStartup"

    + CategoryInfo          : InvalidOperation: (:) [New-Object], MethodInvocationException
    + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand
    + PSComputerName        : localhost

You cannot call a method on a null-valued expression.

At .\flancy\flancy.psm1:395 char:9
+         $flancy.start()
+         ~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

PowerShell version:

Name             : Windows PowerShell ISE Host
Version          : 5.0.10018.0
InstanceId       : 03eac871-dcbd-4e6a-8c7c-b5c018dbcda8
UI               : System.Management.Automation.Internal.Host.InternalHostUserInterface
CurrentCulture   : ru-RU
CurrentUICulture : en-US
PrivateData      : Microsoft.PowerShell.Host.ISE.ISEOptions
DebuggerEnabled  : True
IsRunspacePushed : False
Runspace         : System.Management.Automation.Runspaces.LocalRunspace

This doesn't happen with this version of Flancy.

h0rnman commented 8 years ago

I think this is the same as issue #32

beatcracker commented 8 years ago

@h0rnman Agree. Curious, that I have no issues while running Flancy in PS console/ISE, only as job.

h0rnman commented 8 years ago

Is your ISE environment importing a different build of the module perhaps? I am seeing the error in both ISE and console.

beatcracker commented 8 years ago

I've nailed it! It all comes down to the Flancy's Path parameter. When Flancy is run in the interactive PowerShell session and Path parameter is not specified, it will be set to the current PowerShell's directory. But if you run it as a job, $PWD points to the current user's My Documents folder (C:\Users\Administrator\Documents in my case).

Here is the code in the Flancy.psm1 that sets it:

if (!$path) {
    $path = ''
    if ($MyInvocation.MyCommand.Path) {
        $path = Split-Path $MyInvocation.MyCommand.Path
    } else {
        $path = $pwd -replace '^\S+::',''
    }
}

And that's where weird things start to happen: Nancy tries to access folder C:\Users\Administrator\Documents\My Music which doesn't exist!

System.UnauthorizedAccessException: Access to the path 'C:\Users\Administrator\Documents\My Music' is denied.
    at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
    at System.IO.FileSystemEnumerableIterator`1.AddSearchableDirsToStack(SearchData localSearchData)
    at System.IO.FileSystemEnumerableIterator`1.MoveNext()
    at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
    at System.IO.Directory.InternalGetFileDirectoryNames(String path, String userPathOriginal, String searchPattern, Boolean includeFiles, Boolean includeDirs, SearchOption searchOption, Boolean checkHost)
    at System.IO.Directory.InternalGetFiles(String path, String searchPattern, SearchOption searchOption)
    at Nancy.ViewEngines.DefaultFileSystemReader.GetFilenames(String path, String extension)
    at System.Linq.Enumerable.<SelectManyIterator>d__14`2.MoveNext()
    at System.Linq.Enumerable.<DistinctIterator>d__81`1.MoveNext()
    at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
    at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
    at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
    at Nancy.ViewEngines.DefaultFileSystemReader.GetViewsWithSupportedExtensions(String path, IEnumerable`1 supportedViewExtensions)
    at Nancy.ViewEngines.FileSystemViewLocationProvider.GetViewsFromPath(String path, IEnumerable`1 supportedViewExtensions)
    at Nancy.ViewEngines.DefaultViewLocator..ctor(IViewLocationProvider viewLocationProvider, IEnumerable`1 viewEngines)
    at lambda_method(Closure , Object[] )
    at Nancy.TinyIoc.TinyIoCContainer.ConstructType(Type requestedType, Type implementationType, ConstructorInfo constructor, NamedParameterOverloads parameters, ResolveOptions options)

I'm also able to reproduce this in the PowerShell session (not job). It looks like Nancy is trying to traverse down the given path, because when I set Path to the root of my drive (E:\), exception is thrown about E:\$RECYCLE.BIN\S-1-5-18.

To get this details I've modified Flancy.psm1 to catch and unwind inner exceptions when creating new Flancy object. This gives you the full list of the exceptions, instead of the outer one.

Before:

$flancy = new-object "flancy.flancy" -argumentlist $url

After:

try
{
    $flancy = new-object "flancy.flancy" -argumentlist $url
}
catch
{
    function Unwind-Exception
    {
        Param($Exception)

        if($Exception.InnerException)
        {
            $Exception.InnerException.PsObject.Properties | Select-Object -Property Name, Value
            Unwind-Exception $Exception.InnerException
        }
    }

    Unwind-Exception $_.Exception.InnerException
}
toenuff commented 8 years ago

I will include the exception unwinding in flancy.psm1 in the devel branch. Doing that now. If you set your directory to something else in the job, is it working? I'd like to understand how to make it work. I'd also like to understand what's causing it.

toenuff commented 8 years ago

never mind - I duplicated and was able to get it to work with this:

Start-Job -InitializationScript {import-module c:\flancy\flancy.psd1} -ScriptBlock {cd c:\flancy; new-flancy;while ($true) {sleep 60}}

I'm thinking the best way to fix this would be to create an asjob parameter. I could then do the following:

  1. load the module in the initialization script from get-module -listavailable flancy |select -expandproperty modulebase
  2. Set the current directory in the job to the current directory
  3. Pass any arguments as a single argument to the scriptblock
  4. Pass the arguments to the scriptblock to new-flancy
  5. Create the loop with a 60 minute sleep
toenuff commented 8 years ago

Confirmed, this appears to be a bug in Nancy, not flancy. You cannot seem to set the root path to c:\users\username\documents. I attempted to compile it with that root path and it errors. Actually, I found that any directory (such as root drives due to the recycle bin) where a subfolder gives access denied appears to error. i'm unsure whether or not I can use the following to validate the rootdir just now.

Get-ChildItem c:\users\tome\documents -Depth 1 -force -Recurse -ea stop

It will work for my docs and for c-drive, but I'm unsure if the depth goes further in Nancy. For now, I wiil try adding the validation of the path with the above and catch the error to notify people that they cannot use that directory and to use the -path parameter to override the path.

I opened this in Nancy's issue list: https://github.com/NancyFx/Nancy/issues/2146

toenuff commented 8 years ago

OK - We should be able to close this pending validation that @beatcracker can implement a workaround with -Path and that he now receives a better error message when not using -Path in a job (or changing directory in the job itself prior to calling new-flancy). Once confirmed, we'll close this, and then we'll implement asjob per Issue #25

beatcracker commented 8 years ago

I'm able to run Flancy as job if I specify path without inaccessible directories and I receive a new error message if path contains such inaccessible directories.

But we have to do something about the validation code, because it's very slow - it tries to enumerate all directories/subdirectories in given path. In my case it takes 3 seconds to start Flancy as job when Path targets an empty directory and 25 seconds when Path is set to the drive root (+ heavy HDD activity).

toenuff commented 8 years ago

Well, we could opt for an easier way - I think it might be better anyway: Let's ensure that we are not in the root of a drive or that we are not in $env:userprofile\documents or $enf:userprofile\my documents. As long as path doesn't match those 3, chances are the person is in an ok directory. AND if they are not, they will get the normal access denied errors thanks to your update. Plus, we now have this documented in the parameter and we can update the examples and readme.md to call it out.

I think that's fair.

beatcracker commented 8 years ago

Yes, I was thinking along those lines. In fact, I don't even think that we should block user from supplying those paths, just check and Write-Warning if they match.

beatcracker commented 8 years ago

Just figured out why accessing My Musicgenerates exception in Nancy (of course, it's a permissions issue, but a curious one): https://github.com/NancyFx/Nancy/issues/2146#issuecomment-162368570