2021年4月26日月曜日

アプリケーションを正しく起動できませんでした(0xc000007b) Visual Studio 編

アプリケーションをビルドして実行しようとしたら、突然「アプリケーションを正しく起動できませんでした(0xc000007b)」と言われて、アプリケーションが実行できなくなりました。

バージョン管理システムで、プロジェクトを別ディレクトリに展開したばかりで、どこかプロジェクトの設定に問題がありそうですが、なかなか原因がわかりませんでした。


原因は、リソースのRT_MANIFEST/IDR_MANIFESTにありました。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 
<assemblyIdentity 
    version="1.0.0.0" 
    processorArchitecture="*" 
    name="Microsoft.Windows.GisMaintenance"
    type="win32" 
/> 
<description>アプリケーションの説明をここに挿入します。</description> 
<dependency> 
    <dependentAssembly> 
        <assemblyIdentity 
            type="win32" 
            name="Microsoft.Windows.Common-Controls" 
            version="6.0.0.0" 
            processorArchitecture="x86" 
            publicKeyToken="1234a56789bcdeff" 
            language="*" 
        /> 
    </dependentAssembly> 
</dependency> 
</assembly>

processorArchitecture="x86" とありますが、プロセッサ変わる事があるので
processorArchitecture="*" に変更します

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 
<assemblyIdentity 
    version="1.0.0.0" 
    processorArchitecture="*" 
    name="Microsoft.Windows.GisMaintenance"
    type="win32" 
/> 
<description>アプリケーションの説明をここに挿入します。</description> 
<dependency> 
    <dependentAssembly> 
        <assemblyIdentity 
            type="win32" 
            name="Microsoft.Windows.Common-Controls" 
            version="6.0.0.0" 
            processorArchitecture="*" 
            publicKeyToken="1234a56789bcdeff" 
            language="*" 
        /> 
    </dependentAssembly> 
</dependency> 
</assembly>

古いプロジェクトだと、x86 が入っていたりするので、注意が必要です。書いとかないと、同じ事が起こった時にまた調べる事になりそうで・・・

2021年4月20日火曜日

OpenCV warpPerspective を魔改造


ラスター変換で、図面の画像を縮小変換した場合に、Cubic や Lanczos4 といった周りの画素値から平均を求める手法だと、線が薄くなって消えてしまいます。縮小変換しても線を消さないようにするために、周りの画素値の最小値を取ってやれば良いと思い魔改造する事に・・・。

魔改造を施したのは OpenCV 4.5.2 のバージョンです。

/modules/imgproc/src/imgwarp.cpp
を改変していきます。尚、OpenCL や OpenVX など、コンパイル条件で多様なルーチンを選択できますが、最小限のコードのみを対象としました。
warpPerspectiveでは cv::INTER_AREA というフラグを指定しても cv::INTER_LINEAR というフラグに変換されて処理されていたので、cv::INTER_AREA が指定された場合に、線画用の縮小処理が実行されるという方針で実装しました

手っ取り早くBilinear のコードをベースに改造します。
係数配列を追加します。
static float AreasupTab_f[INTER_TAB_SIZE2][4][4];
static short AreasupTab_i[INTER_TAB_SIZE2][4][4];

初期化関数を追加します。不要なんですが、体裁だけ整えます。
static inline void interpolateAreasup( float x, float* coeffs )
{
    coeffs[0] = coeffs[1] = coeffs[2] = coeffs[3] = 0;
}

cv::INTER_AREAが指定された時に初期化されるコードを追加します
static const void* initInterTab2D( int method, bool fixpt )
{
    static bool inittab[INTER_MAX+1] = {false};
    float* tab = 0;
    short* itab = 0;
    int ksize = 0;
    if( method == INTER_LINEAR )
        tab = BilinearTab_f[0][0], itab = BilinearTab_i[0][0], ksize=2;
    else if( method == INTER_CUBIC )
        tab = BicubicTab_f[0][0], itab = BicubicTab_i[0][0], ksize=4;
    else if( method == INTER_LANCZOS4 )
        tab = Lanczos4Tab_f[0][0], itab = Lanczos4Tab_i[0][0], ksize=8;
        // --> ここから
    else if( method == INTER_AREA )
        tab = AreasupTab_f[0][0], itab = AreasupTab_i[0][0], ksize=4;
        // --> ここまで追加
    else
        CV_Error( CV_StsBadArg, "Unknown/unsupported interpolation type" );
    //...

係数配列を全部初期化する関数にもcv::INTER_AREA の初期化を追加します
static bool initAllInterTab2D()
{
    return  initInterTab2D( INTER_LINEAR, false ) &&
            initInterTab2D( INTER_LINEAR, true ) &&
            initInterTab2D( INTER_CUBIC, false ) &&
            initInterTab2D( INTER_CUBIC, true ) &&
            initInterTab2D( INTER_LANCZOS4, false ) &&
            initInterTab2D( INTER_LANCZOS4, true ) &&
            initInterTab2D( INTER_AREA, false ) && // この辺
            initInterTab2D( INTER_AREA, true );
}

処理の核心部を追加します
template<class CastOp, typename AT, int ONE>
static void remapAreasup( const Mat& _src, Mat& _dst, const Mat& _xy,
                          const Mat& _fxy, const void* _wtab,
                          int borderType, const Scalar& _borderValue )
{
    typedef typename CastOp::rtype T;
    typedef typename CastOp::type1 WT;
    Size ssize = _src.size(), dsize = _dst.size();
    const int cn = _src.channels();
    const AT* wtab = (const AT*)_wtab;
    const T* S0 = _src.ptr<T>();
    size_t sstep = _src.step/sizeof(S0[0]);
    T cval[CV_CN_MAX];
    CastOp castOp;

    for(int k = 0; k < cn; k++ )
        cval[k] = saturate_cast<T>(_borderValue[k & 3]);

    int borderType1 = borderType != BORDER_TRANSPARENT ? borderType : BORDER_REFLECT_101;

    unsigned width1 = std::max(ssize.width-3, 0), height1 = std::max(ssize.height-3, 0);

    if( _dst.isContinuous() && _xy.isContinuous() && _fxy.isContinuous() )
    {
        dsize.width *= dsize.height;
        dsize.height = 1;
    }

    for(int dy = 0; dy < dsize.height; dy++ )
    {
        T* D = _dst.ptr<T>(dy);
        const short* XY = _xy.ptr<short>(dy);
        const ushort* FXY = _fxy.ptr<ushort>(dy);

        for(int dx = 0; dx < dsize.width; dx++, D += cn )
        {
            int sx = XY[dx*2]-1, sy = XY[dx*2+1]-1;
            //const AT* w = wtab + FXY[dx]*16;
            if( (unsigned)sx < width1 && (unsigned)sy < height1 )
            {
                const T* S = S0 + sy*sstep + sx*cn;
                for(int k = 0; k < cn; k++ )
                {
                    WT sum = std::min<WT>(std::min<WT>(S[0],S[cn]), std::min<WT>(S[cn*2], S[cn*3]));
                    S += sstep;
                    sum = std::min<WT>(sum, std::min<WT>(std::min(S[0],S[cn]), std::min<WT>(S[cn*2], S[cn*3])));
                    S += sstep;
                    sum = std::min<WT>(sum, std::min<WT>(std::min(S[0],S[cn]), std::min<WT>(S[cn*2], S[cn*3])));
                    S += sstep;
                    sum = std::min<WT>(sum, std::min<WT>(std::min(S[0],S[cn]), std::min<WT>(S[cn*2], S[cn*3])));
                    S += 1 - sstep*3;
                    D[k] = static_cast<WT>(sum);
                }
            }
            else
            {
                int x[4], y[4];
                if( borderType == BORDER_TRANSPARENT &&
                    ((unsigned)(sx+1) >= (unsigned)ssize.width ||
                    (unsigned)(sy+1) >= (unsigned)ssize.height) ) {
                      continue;
                }

                if( borderType1 == BORDER_CONSTANT &&
                    (sx >= ssize.width || sx+4 <= 0 ||
                    sy >= ssize.height || sy+4 <= 0))
                {
                    for(int k = 0; k < cn; k++ )
                        D[k] = cval[k];
                    continue;
                }

                for(int i = 0; i < 4; i++ )
                {
                    x[i] = borderInterpolate(sx + i, ssize.width, borderType1)*cn;
                    y[i] = borderInterpolate(sy + i, ssize.height, borderType1);
                }

                for(int k = 0; k < cn; k++, S0++ )
                {
                  WT cv = cval[k], sum = std::numeric_limits<WT>::max();
                    for(int i = 0; i < 4; i++ )
                    {
                        int yi = y[i];
                        const T* S = S0 + yi*sstep;
                        if( yi < 0 )
                            continue;
                        if( x[0] >= 0 )
                            sum = std::min<WT>(sum, S[x[0]]);
                        if( x[1] >= 0 )
                            sum = std::min<WT>(sum, S[x[1]]);
                        if( x[2] >= 0 )
                            sum = std::min<WT>(sum, S[x[2]]);
                        if( x[3] >= 0 )
                            sum = std::min<WT>(sum, S[x[3]]);
                    }
                    D[k] = static_cast<WT>(sum);
                }
                S0 -= cn;
            }
        }
    }
}

remap に cv::INTER_AREA 時の処理を追加します。IPPとかは無視です。
void cv::remap( InputArray _src, OutputArray _dst,
                InputArray _map1, InputArray _map2,
                int interpolation, int borderType, const Scalar& borderValue )
{
    CV_INSTRUMENT_REGION();

    static RemapNNFunc nn_tab[] =
    {
        remapNearest<uchar>, remapNearest<schar>, remapNearest<ushort>, remapNearest<short>,
        remapNearest<int>, remapNearest<float>, remapNearest<double>, 0
    };

    static RemapFunc linear_tab[] =
    {
        remapBilinear<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, RemapVec_8u, short>, 0,
        remapBilinear<Cast<float, ushort>, RemapNoVec, float>,
        remapBilinear<Cast<float, short>, RemapNoVec, float>, 0,
        remapBilinear<Cast<float, float>, RemapNoVec, float>,
        remapBilinear<Cast<double, double>, RemapNoVec, float>, 0
    };

    static RemapFunc cubic_tab[] =
    {
        remapBicubic<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, short, INTER_REMAP_COEF_SCALE>, 0,
        remapBicubic<Cast<float, ushort>, float, 1>,
        remapBicubic<Cast<float, short>, float, 1>, 0,
        remapBicubic<Cast<float, float>, float, 1>,
        remapBicubic<Cast<double, double>, float, 1>, 0
    };

    static RemapFunc lanczos4_tab[] =
    {
        remapLanczos4<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, short, INTER_REMAP_COEF_SCALE>, 0,
        remapLanczos4<Cast<float, ushort>, float, 1>,
        remapLanczos4<Cast<float, short>, float, 1>, 0,
        remapLanczos4<Cast<float, float>, float, 1>,
        remapLanczos4<Cast<double, double>, float, 1>, 0
    };

    // --> ここから
    static RemapFunc areasup_tab[] = 
    {
        remapAreasup<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, short, INTER_REMAP_COEF_SCALE>, 0,
        remapAreasup<Cast<float, ushort>, float, 1>,
        remapAreasup<Cast<float, short>, float, 1>, 0,
        remapAreasup<Cast<float, float>, float, 1>,
        remapAreasup<Cast<double, double>, float, 1>, 0
    };
    // --> ここまで追加
  
    CV_Assert( !_map1.empty() );
    CV_Assert( _map2.empty() || (_map2.size() == _map1.size()));

    CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat(),
               ocl_remap(_src, _dst, _map1, _map2, interpolation, borderType, borderValue))

    Mat src = _src.getMat(), map1 = _map1.getMat(), map2 = _map2.getMat();
    _dst.create( map1.size(), src.type() );
    Mat dst = _dst.getMat();


    CV_OVX_RUN(
        src.type() == CV_8UC1 && dst.type() == CV_8UC1 &&
        !ovx::skipSmallImages<VX_KERNEL_REMAP>(src.cols, src.rows) &&
        (borderType& ~BORDER_ISOLATED) == BORDER_CONSTANT &&
        ((map1.type() == CV_32FC2 && map2.empty() && map1.size == dst.size) ||
         (map1.type() == CV_32FC1 && map2.type() == CV_32FC1 && map1.size == dst.size && map2.size == dst.size) ||
         (map1.empty() && map2.type() == CV_32FC2 && map2.size == dst.size)) &&
        ((borderType & BORDER_ISOLATED) != 0 || !src.isSubmatrix()),
        openvx_remap(src, dst, map1, map2, interpolation, borderValue));

    CV_Assert( dst.cols < SHRT_MAX && dst.rows < SHRT_MAX && src.cols < SHRT_MAX && src.rows < SHRT_MAX );

    if( dst.data == src.data )
        src = src.clone();

    // --> 使われていない INTER_AREA を利用するので、以下コメントアウトして有効化
    //if( interpolation == INTER_AREA )
    //    interpolation = INTER_LINEAR;

    int type = src.type(), depth = CV_MAT_DEPTH(type);

#if defined HAVE_IPP && !IPP_DISABLE_REMAP
    CV_IPP_CHECK()
    {
        if ((interpolation == INTER_LINEAR || interpolation == INTER_CUBIC || interpolation == INTER_NEAREST) &&
                map1.type() == CV_32FC1 && map2.type() == CV_32FC1 &&
                (borderType == BORDER_CONSTANT || borderType == BORDER_TRANSPARENT))
        {
            int ippInterpolation =
                interpolation == INTER_NEAREST ? IPPI_INTER_NN :
                interpolation == INTER_LINEAR ? IPPI_INTER_LINEAR : IPPI_INTER_CUBIC;

            ippiRemap ippFunc =
                type == CV_8UC1 ? (ippiRemap)ippiRemap_8u_C1R :
                type == CV_8UC3 ? (ippiRemap)ippiRemap_8u_C3R :
                type == CV_8UC4 ? (ippiRemap)ippiRemap_8u_C4R :
                type == CV_16UC1 ? (ippiRemap)ippiRemap_16u_C1R :
                type == CV_16UC3 ? (ippiRemap)ippiRemap_16u_C3R :
                type == CV_16UC4 ? (ippiRemap)ippiRemap_16u_C4R :
                type == CV_32FC1 ? (ippiRemap)ippiRemap_32f_C1R :
                type == CV_32FC3 ? (ippiRemap)ippiRemap_32f_C3R :
                type == CV_32FC4 ? (ippiRemap)ippiRemap_32f_C4R : 0;

            if (ippFunc)
            {
                bool ok;
                IPPRemapInvoker invoker(src, dst, map1, map2, ippFunc, ippInterpolation,
                                        borderType, borderValue, &ok);
                Range range(0, dst.rows);
                parallel_for_(range, invoker, dst.total() / (double)(1 << 16));

                if (ok)
                {
                    CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT);
                    return;
                }
                setIppErrorStatus();
            }
        }
    }
#endif

    RemapNNFunc nnfunc = 0;
    RemapFunc ifunc = 0;
    const void* ctab = 0;
    bool fixpt = depth == CV_8U;
    bool planar_input = false;

    if( interpolation == INTER_NEAREST )
    {
        nnfunc = nn_tab[depth];
        CV_Assert( nnfunc != 0 );
    }
    else
    {
        if( interpolation == INTER_LINEAR )
            ifunc = linear_tab[depth];
        else if( interpolation == INTER_CUBIC ){
            ifunc = cubic_tab[depth];
            CV_Assert( _src.channels() <= 4 );
        }
        else if( interpolation == INTER_LANCZOS4 ){
            ifunc = lanczos4_tab[depth];
            CV_Assert( _src.channels() <= 4 );
        }
        // --> ここから
        else if( interpolation == INTER_AREA ) {
            ifunc = areasup_tab[depth];
            CV_Assert( _src.channels() <= 4 );
        }
        // --> ここまで追加
        else
            CV_Error( CV_StsBadArg, "Unknown interpolation method" );
        CV_Assert( ifunc != 0 );
        ctab = initInterTab2D( interpolation, fixpt );
    }

    const Mat *m1 = &map1, *m2 = &map2;

    if( (map1.type() == CV_16SC2 && (map2.type() == CV_16UC1 || map2.type() == CV_16SC1 || map2.empty())) ||
        (map2.type() == CV_16SC2 && (map1.type() == CV_16UC1 || map1.type() == CV_16SC1 || map1.empty())) )
    {
        if( map1.type() != CV_16SC2 )
            std::swap(m1, m2);
    }
    else
    {
        CV_Assert( ((map1.type() == CV_32FC2 || map1.type() == CV_16SC2) && map2.empty()) ||
            (map1.type() == CV_32FC1 && map2.type() == CV_32FC1) );
        planar_input = map1.channels() == 1;
    }

    RemapInvoker invoker(src, dst, m1, m2,
                         borderType, borderValue, planar_input, nnfunc, ifunc,
                         ctab);
    parallel_for_(Range(0, dst.rows), invoker, dst.total()/(double)(1<<16));
}

成果
 変換元画像

 cv::INTER_LINEAR で変換

 魔改造 cv::INTER_AREA で変換

2021年2月17日水曜日

OpenCV copy between cv::Mat and Windows BITMAP 忘備録

opencv のコードを一部修正したくて、vcpkg の opencv を update したら、バージョン 3.4.3 から 4.3.0 にアップデートされた。
既存コードをコンパイルしてみると、IplImage が定義されていないと怒られてしまう。どうやら、IplImage が deprecated になったようだ。

どこで IplImage を使っていたかと言うと、BITMAPハンドルとcv::Matの相互変換部分。
cv::Mat BitmapToMat(HANDLE hBitmap) {
  cv::Mat result;
  {
    BITMAP bm;
    GetObject(hBitmap, sizeof(BITMAP), &bm );
    result = cv::Mat( bm.bmHeight, bm.bmWidth, CV_8UC4, cv::Scalar(255,255,255,255) );
    IplImage img = result;
    memcpy( img.imageData, bm.bmBits, bm.bmWidthBytes * bm.bmHeight );
  }
  return result;
}

HBITMAP MatToBitmap(const cv::Mat& src, HDC hMemDC) {
  HBITMAP hBitmap = NULL;
  if( !src.empty() && src.cols > 0 && src.rows > 0 ) {
    BITMAPINFO bi;
    bi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
    bi.bmiHeader.biWidth       = src.cols;
    bi.bmiHeader.biHeight      = src.rows;
    bi.bmiHeader.biPlanes      = 1;
    bi.bmiHeader.biBitCount    = 32;
    bi.bmiHeader.biCompression = BI_RGB;
    {
      LPDWORD lpPixels;
      hBitmap = CreateDIBSection( hMemDC, &bi, DIB_RGB_COLORS, reinterpret_cast<void**>(&lpPixels), NULL, 0 );
      GdiFlush();
    }
    BITMAP bm;
    GetObject(hBitmap, sizeof(BITMAP), &bm);
    cv::Mat rev;
    cv::flip(src, rev, 0);
    IplImage img = rev;
    memcpy( bm.bmBits, img.imageData, src.cols * src.rows * 4 );
  }
  return hBitmap;
}
どう手直ししたら良いのかわからなくて、ググってみたが情報が少ない。
これは需要があるかもしれん?と思って、ここに記すことにした
cv::Mat BitmapToMat(HANDLE hBitmap) {
  cv::Mat result;
  {
    BITMAP bm;
    GetObject(hBitmap, sizeof(BITMAP), &bm );
    result = cv::Mat( bm.bmHeight, bm.bmWidth, CV_8UC4, (void*)bm.bmBits );
  }
  return result;
}

HBITMAP MatToBitmap(const cv::Mat& src, HDC hMemDC) {
  HBITMAP hBitmap = NULL;
  if( !src.empty() && src.cols > 0 && src.rows > 0 ) {
    BITMAPINFO bi;
    bi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
    bi.bmiHeader.biWidth       = src.cols;
    bi.bmiHeader.biHeight      = src.rows;
    bi.bmiHeader.biPlanes      = 1;
    bi.bmiHeader.biBitCount    = 32;
    bi.bmiHeader.biCompression = BI_RGB;
    {
      LPDWORD lpPixels;
      hBitmap = CreateDIBSection( hMemDC, &bi, DIB_RGB_COLORS, reinterpret_cast<void**>(&lpPixels), NULL, 0 );
      GdiFlush();
    }
    BITMAP bm;
    GetObject(hBitmap, sizeof(BITMAP), &bm);
    cv::Mat rev;
    cv::flip(src, rev, 0);
    memcpy( bm.bmBits, rev.ptr(), src.cols * src.rows * 4 );
  }
  return hBitmap;
}
ポインタをダイレクトに扱っているので、適正に使用しないと Abnormal Termination を引き起こすでしょう。

2021年2月3日水曜日

Proj4 C++ 座標変換忘備録

Proj4 ver 6.3.1 で座標変換するコードの習作
#include <proj.h>
#include <iostream>

int main() {

  PJ_CONTEXT* C;
  PJ*         P;
  PJ*         P_for_GIS;

  C = proj_context_create();
  P = proj_create_crs_to_crs( C, "EPSG:3857", "EPSG:6679", NULL );
  if( !P ) {
    std::cerr << "Fail to create proj crs conversion" << std::endl;
    return 1;
  }
  P_for_GIS = proj_normalize_for_visualization(C,P);
  if( !P_for_GIS ) {
    std::cerr << "Fail to normalize proj" << std::endl;
    return 1;
  }
  proj_destroy(P);
  P = P_for_GIS;
  
  PJ_COORD sp = proj_coord(15734773.4,5323579.9,0,0);
  
  PJ_COORD dp = proj_trans(P, PJ_FWD, sp);
  std::cout << std::fixed << sp.v[0] << "," << sp.v[1] << std::endl;
  std::cout << " to ";
  std::cout << dp.v[0] << "," << dp.v[1] << std::endl;
  
  proj_destroy(P);
  proj_context_destroy(C);
  
  return 0;
}
参考: https://github.com/OSGeo/PROJ/blob/master/examples/pj_obs_api_mini_demo.c

2020年7月20日月曜日

WSUSの管理

 Windows Update から最新のセキュリティパッチを当てたいけど、サーバからのインターネット・アクセスは遮断したいよね?とか、会社の全パソコンから Microsoft Update にパッチ取りに行くのはナンセンスだよね?とか思うと、WSUS を運用する事になります。

ところが、このメジャーなWSUSの管理に関しては、ほとんど情報が無い。何故なのか?わかりません。理解に苦しみます。
しかも、このWSUS。メンテナンスを怠るとクソ重たくなって動かなくなる始末。何回、再インストールをした事か?
ほんと、素晴らしいツールである事に間違いは無いのですが、メンテナンスがしんどくて、こいつのお守りにリソースを食われます。

ここで紹介するツールは、Microsoft のブログで紹介された WSUS管理のためのScriptで、拾い物です。
ネットで検索しても見つからないので、ここに紹介します(著作権がわからないので、掲載を取りやめるかもしれません)

トップバッターは、古い更新を拒否して、新しい更新を許可するスクリプトです。残念ながら Windows Defender 関連のアップデートが対象外となってます。どなたか、わかりましたら教えてください
# ===============================================
# Script to decline superseeded updates in WSUS.
# ===============================================
# It's recommended to run the script with the -SkipDecline switch to see how many superseded updates are in WSUS and to TAKE A BACKUP OF THE SUSDB before declining the updates.
# Parameters:

# $UpdateServer             = Specify WSUS Server Name
#$UpdateServer = "wsus.your.domain";
# $UseSSL                   = Specify whether WSUS Server is configured to use SSL
# $Port                     = Specify WSUS Server Port
#$Port = 8530;
# $SkipDecline              = Specify this to do a test run and get a summary of how many superseded updates we have
# $DeclineLastLevelOnly     = Specify whether to decline all superseded updates or only last level superseded updates

# Supersedence chain could have multiple updates. 
# For example, Update1 supersedes Update2. Update2 supersedes Update3. In this scenario, the Last Level in the supersedence chain is Update3. 
# To decline only the last level updates in the supersedence chain, specify the DeclineLastLevelOnly switch

# Usage:
# =======

# To do a test run against WSUS Server without SSL
# Decline-SupersededUpdates.ps1 -UpdateServer SERVERNAME -Port 8530 -SkipDecline

# To do a test run against WSUS Server using SSL
# Decline-SupersededUpdates.ps1 -UpdateServer SERVERNAME -UseSSL -Port 8531 -SkipDecline

# To decline all superseded updates on the WSUS Server using SSL
# Decline-SupersededUpdates.ps1 -UpdateServer SERVERNAME -UseSSL -Port 8531

# To decline only Last Level superseded updates on the WSUS Server using SSL
# Decline-SupersededUpdates.ps1 -UpdateServer SERVERNAME -UseSSL -Port 8531 -DeclineLastLevelOnly

[CmdletBinding()]
Param(
 [Parameter(Mandatory=$True,Position=1)]
    [string] $UpdateServer,
 
 [Parameter(Mandatory=$False)]
    [switch] $UseSSL,
 
 [Parameter(Mandatory=$True, Position=2)]
    $Port,
    [switch] $SkipDecline,
    [switch] $DeclineLastLevelOnly
)

Write-Host ""

if ($SkipDecline -and $DeclineLastLevelOnly) {
    Write-Host "Using SkipDecline and DeclineLastLevelOnly switches together is not allowed."
 Write-Host ""
    return
}

$outPath = Split-Path $script:MyInvocation.MyCommand.Path
$outSupersededList = Join-Path $outPath "SupersededUpdates.csv"
$outSupersededListBackup = Join-Path $outPath "SupersededUpdatesBackup.csv"
"UpdateID, RevisionNumber, Title, KBArticle, SecurityBulletin, LastLevel" | Out-File $outSupersededList

try {
    
    if ($UseSSL) {
        Write-Host "Connecting to WSUS server $UpdateServer on Port $Port using SSL... " -NoNewLine
    } Else {
        Write-Host "Connecting to WSUS server $UpdateServer on Port $Port... " -NoNewLine
    }
    
    [reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration") | out-null
    #$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer($UpdateServer, $UseSSL, $Port);
 $wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer();
}
catch [System.Exception] 
{
    Write-Host "Failed to connect."
    Write-Host "Error:" $_.Exception.Message
    Write-Host "Please make sure that WSUS Admin Console is installed on this machine"
 Write-Host ""
    $wsus = $null
}

if ($wsus -eq $null) { return } 

Write-Host "Connected."

$countAllUpdates = 0
$countSupersededAll = 0
$countSupersededLastLevel = 0
$countDeclined = 0

Write-Host "Getting a list of all updates... " -NoNewLine

try {
 $allUpdates = $wsus.GetUpdates()
}

catch [System.Exception]
{
 Write-Host "Failed to get updates."
 Write-Host "Error:" $_.Exception.Message
    Write-Host "If this operation timed out, please decline the superseded updates from the WSUS Console manually."
 Write-Host ""
 return
}

Write-Host "Done"

Write-Host "Parsing the list of updates... " -NoNewLine
foreach($update in $allUpdates) {
    
    $countAllUpdates++
    
    if ($update.IsDeclined) {
        $countDeclined++
    }
    
    if (!$update.IsDeclined -and $update.IsSuperseded) {
        $countSupersededAll++
        
        if (!$update.HasSupersededUpdates) {
            $countSupersededLastLevel++
        }
        
        "$($update.Id.UpdateId.Guid), $($update.Id.RevisionNumber), $($update.Title), $($update.KnowledgeBaseArticles), $($update.SecurityBulletins), $($update.HasSupersededUpdates)" | Out-File $outSupersededList -Append       
        
    }
}

Write-Host "Done."
Write-Host "List of superseded updates: $outSupersededList"

Write-Host ""
Write-Host "Summary:"
Write-Host "========"

Write-Host "All Updates =" $countAllUpdates
Write-Host "Any except Declined =" ($countAllUpdates - $countDeclined)
Write-Host "All Superseded Updates =" $countSupersededAll
Write-Host "    Superseded Updates (Intermediate) =" ($countSupersededAll - $countSupersededLastLevel)
Write-Host "    Superseded Updates (Last Level) =" $countSupersededLastLevel
Write-Host ""

if (!$SkipDecline) {
    
    Write-Host "SkipDecline flag is set to $SkipDecline. Continuing with declining updates"
    $updatesDeclined = 0
    
    if ($DeclineLastLevelOnly) {
        Write-Host "  DeclineLastLevel is set to True. Only declining last level superseded updates." 
        
        foreach ($update in $allUpdates) {
            
            if (!$update.IsDeclined -and $update.IsSuperseded -and !$update.HasSupersededUpdates) {
                
                try 
                {
                    $update.Decline()
                    # Write-Host "Declined update $($update.Id.UpdateId.Guid)"
                    Write-Progress -Activity "Declining Updates" -Status "Declining update $($update.Id.UpdateId.Guid)" -PercentComplete (($updatesDeclined/$countSupersededLastLevel) * 100)
                    $updatesDeclined++
                }
                catch [System.Exception]
                {
                    Write-Host "Failed to decline update $($update.Id.UpdateId.Guid). Error:" $_.Exception.Message
                }            
            }
        }        
    }
    else {
        Write-Host "  DeclineLastLevel is set to False. Declining all superseded updates."
        
        foreach ($update in $allUpdates) {
            
            if (!$update.IsDeclined -and $update.IsSuperseded) {
                
                try 
                {
                    $update.Decline()
                    # Write-Host "Declined update $($update.Id.UpdateId.Guid)"
                    Write-Progress -Activity "Declining Updates" -Status "Declining update $($update.Id.UpdateId.Guid)" -PercentComplete (($updatesDeclined/$countSupersededAll) * 100)
                    $updatesDeclined++
                }
                catch [System.Exception]
                {
                    Write-Host "Failed to decline update $($update.Id.UpdateId.Guid). Error:" $_.Exception.Message
                }            
            }
        }   
        
    }
    
    Write-Host "  Declined $updatesDeclined updates."
    if ($updatesDeclined -ne 0) {
        Copy-Item -Path $outSupersededList -Destination $outSupersededListBackup -Force
  Write-Host "  Backed up list of superseded updates to $outSupersededListBackup"
    }
    
}
else {
    Write-Host "SkipDecline flag is set to $SkipDecline. Skipped declining updates"
}

Write-Host ""
Write-Host "Done"
Write-Host ""

2番手は、クリーンアップのスクリプト2種です。中身理解してません。
きれいにして
[System.Reflection.Assembly]::LoadWithPartialName('microsoft.updateservices.administration')

$wsus=new-object 'Microsoft.UpdateServices.Administration.AdminProxy'
$wsusrv=$wsus.GetUpdateServerInstance()

$cm=$Wsusrv.GetCleanupManager()
$cs=new-object 'Microsoft.UpdateServices.Administration.CleanupScope'

$cs.CleanupObsoleteUpdates = $True

$cm.PerformCleanup($cs)
圧縮するという感じでしょうか?
[System.Reflection.Assembly]::LoadWithPartialName('microsoft.updateservices.administration')

$wsus=new-object 'Microsoft.UpdateServices.Administration.AdminProxy'
$wsusrv=$wsus.GetUpdateServerInstance()

$cm=$Wsusrv.GetCleanupManager()
$cs=new-object 'Microsoft.UpdateServices.Administration.CleanupScope'

$cs.CompressUpdates = $True

$cm.PerformCleanup($cs)

おまけで、PowerShell の実行ポリシーを変更するスクリプトです。石橋を叩いて割るセキュリティには、うんざりです。このスクリプトも効果があるのか無いのか、よくわかりません。これもどっかからの拾い物だったかと…
@ECHO OFF

REM ==============================================================
ECHO Now Checking Windows x64/x86 version information......
ECHO .
ECHO ..
ECHO ...
ECHO ....
ECHO .....
ECHO.
IF EXIST %SystemRoot%\syswow64\WindowsPowerShell\v1.0\powershell.exe GOTO x64
GOTO x86

REM ==============================================================

:x64
REM ==============================================================

ECHO ** Your System was detected as x64bit Windows **
ECHO.
ECHO ----------------------------------------
Echo x86版 PowerShell の LocalMachine 実行ポリシー変更
ECHO ----------------------------------------

ECHO ** Currenct ExecutionPolisy **
%SystemRoot%\syswow64\WindowsPowerShell\v1.0\powershell.exe -Command "Get-ExecutionPolicy -list"

ECHO.
ECHO  ** Changing ExecutionPolisy **
%SystemRoot%\syswow64\WindowsPowerShell\v1.0\powershell.exe -Command "Set-ExecutionPolicy RemoteSigned -Scope LocalMachine -Confirm"

ECHO.
ECHO  ** Changed After ExecutionPolisy **
%SystemRoot%\syswow64\WindowsPowerShell\v1.0\powershell.exe -Command "Get-ExecutionPolicy -list"

ECHO ----------------------------------------
Echo x64版 PowerShell の LocalMachine 実行ポリシー変更
ECHO ----------------------------------------

ECHO  ** Currenct ExecutionPolisy **
%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -Command "Get-ExecutionPolicy -list"

ECHO.
ECHO  ** Changing ExecutionPolisy **
%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -Command "Set-ExecutionPolicy RemoteSigned -Scope LocalMachine -Confirm"

ECHO.
ECHO  ** Changed After ExecutionPolisy **
%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -Command "Get-ExecutionPolicy -list"

GOTO EOF

REM ==============================================================

:x86
REM ==============================================================

ECHO ** Your System was detected as x86bit Windows **
ECHO.
ECHO ----------------------------------------
Echo x86版 PowerShell の LocalMachine 実行ポリシー変更
ECHO ----------------------------------------

ECHO  ** Currenct ExecutionPolisy **
%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -Command "Get-ExecutionPolicy -list"

ECHO.
ECHO  ** Changing ExecutionPolisy **
%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -Command "Set-ExecutionPolicy RemoteSigned -Scope LocalMachine -Confirm"

ECHO.
ECHO  ** Changed After ExecutionPolisy **
%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -Command "Get-ExecutionPolicy -list"

GOTO EOF

REM ==============================================================

:EOF
PAUSE

最後は、SQL Server のメンテナンス用SQLです
やる必要があるのかどうかさえ、理解していません。製品内容が複雑で専門的すぎて、DBには精通している方だと思う自分にも何をしているのか理解できない代物です。データベースが重たすぎてファッキュン!って時に神頼みで実行します。
-- Microsoft SQL Server Management を開く
-- クエリーを作成、これを貼り付ける
-- データベースに「SUSDB」を選択し、クエリを実行する
-- http://www.sqlmusings.com
-- Ensure a USE  statement has been executed first.
SET NOCOUNT ON

-- adapted from "Rebuild or reorganize indexes (with configuration)" from MSDN Books Online 
-- (http://msdn.microsoft.com/en-us/library/ms188917.aspx)
 
-- =======================================================
-- || Configuration variables:
-- || - 10 is an arbitrary decision point at which to
-- || reorganize indexes.
-- || - 30 is an arbitrary decision point at which to
-- || switch from reorganizing, to rebuilding.
-- || - 0 is the default fill factor. Set this to a
-- || a value from 1 to 99, if needed.
-- =======================================================
DECLARE @reorg_frag_thresh   float  SET @reorg_frag_thresh   = 10.0
DECLARE @rebuild_frag_thresh float  SET @rebuild_frag_thresh = 30.0
DECLARE @fill_factor         tinyint SET @fill_factor         = 80
DECLARE @report_only         bit   SET @report_only         = 1

-- added (DS) : page_count_thresh is used to check how many pages the current table uses
DECLARE @page_count_thresh  smallint SET @page_count_thresh   = 1000
 
-- Variables required for processing.
DECLARE @objectid       int
DECLARE @indexid        int
DECLARE @partitioncount bigint
DECLARE @schemaname     nvarchar(130) 
DECLARE @objectname     nvarchar(130) 
DECLARE @indexname      nvarchar(130) 
DECLARE @partitionnum   bigint
DECLARE @partitions     bigint
DECLARE @frag           float
DECLARE @page_count     int
DECLARE @command        nvarchar(4000)
DECLARE @intentions     nvarchar(4000)
DECLARE @table_var      TABLE(
                          objectid     int,
                          indexid      int,
                          partitionnum int,
                          frag         float,
          page_count   int
                        )
 
-- Conditionally select tables and indexes from the
-- sys.dm_db_index_physical_stats function and
-- convert object and index IDs to names.
INSERT INTO
    @table_var
SELECT
    [object_id]                    AS objectid,
    [index_id]                     AS indexid,
    [partition_number]             AS partitionnum,
    [avg_fragmentation_in_percent] AS frag,
 [page_count]       AS page_count
FROM
    sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL , NULL, 'LIMITED')
WHERE
    [avg_fragmentation_in_percent] > @reorg_frag_thresh 
 AND
 page_count > @page_count_thresh
 AND
    index_id > 0
 
 
-- Declare the cursor for the list of partitions to be processed.
DECLARE partitions CURSOR FOR
    SELECT * FROM @table_var
 
-- Open the cursor.
OPEN partitions
 
-- Loop through the partitions.
WHILE (1=1) BEGIN
    FETCH NEXT
        FROM partitions
        INTO @objectid, @indexid, @partitionnum, @frag, @page_count
 
    IF @@FETCH_STATUS < 0 BREAK
 
    SELECT
        @objectname = QUOTENAME(o.[name]),
        @schemaname = QUOTENAME(s.[name])
    FROM
        sys.objects AS o WITH (NOLOCK)
        JOIN sys.schemas as s WITH (NOLOCK)
        ON s.[schema_id] = o.[schema_id]
    WHERE
        o.[object_id] = @objectid
 
    SELECT
        @indexname = QUOTENAME([name])
    FROM
        sys.indexes WITH (NOLOCK)
    WHERE
        [object_id] = @objectid AND
        [index_id] = @indexid
 
    SELECT
        @partitioncount = count (*)
    FROM
        sys.partitions WITH (NOLOCK)
    WHERE
        [object_id] = @objectid AND
        [index_id] = @indexid
 
    -- Build the required statement dynamically based on options and index stats.
    SET @intentions =
        @schemaname + N'.' +
        @objectname + N'.' +
        @indexname + N':' + CHAR(13) + CHAR(10)
    SET @intentions =
        REPLACE(SPACE(LEN(@intentions)), ' ', '=') + CHAR(13) + CHAR(10) +
        @intentions
    SET @intentions = @intentions +
        N' FRAGMENTATION: ' + CAST(@frag AS nvarchar) + N'%' + CHAR(13) + CHAR(10) +
        N' PAGE COUNT: '    + CAST(@page_count AS nvarchar) + CHAR(13) + CHAR(10)
 
    IF @frag < @rebuild_frag_thresh BEGIN
        SET @intentions = @intentions +
            N' OPERATION: REORGANIZE' + CHAR(13) + CHAR(10)
        SET @command =
            N'ALTER INDEX ' + @indexname +
            N' ON ' + @schemaname + N'.' + @objectname +
            N' REORGANIZE; ' + 
            N' UPDATE STATISTICS ' + @schemaname + N'.' + @objectname + 
            N' ' + @indexname + ';'

    END
    IF @frag >= @rebuild_frag_thresh BEGIN
        SET @intentions = @intentions +
            N' OPERATION: REBUILD' + CHAR(13) + CHAR(10)
        SET @command =
            N'ALTER INDEX ' + @indexname +
            N' ON ' + @schemaname + N'.' +     @objectname +
            N' REBUILD'
    END
    IF @partitioncount > 1 BEGIN
        SET @intentions = @intentions +
            N' PARTITION: ' + CAST(@partitionnum AS nvarchar(10)) + CHAR(13) + CHAR(10)
        SET @command = @command +
            N' PARTITION=' + CAST(@partitionnum AS nvarchar(10))
    END
    IF @frag >= @rebuild_frag_thresh AND @fill_factor > 0 AND @fill_factor < 100 BEGIN
        SET @intentions = @intentions +
            N' FILL FACTOR: ' + CAST(@fill_factor AS nvarchar) + CHAR(13) + CHAR(10)
        SET @command = @command +
            N' WITH (FILLFACTOR = ' + CAST(@fill_factor AS nvarchar) + ')'
    END
 
    -- Execute determined operation, or report intentions
    IF @report_only = 0 BEGIN
        SET @intentions = @intentions + N' EXECUTING: ' + @command
        PRINT @intentions     
        EXEC (@command)
    END ELSE BEGIN
        PRINT @intentions
    END
 PRINT @command

END
 
-- Close and deallocate the cursor.
CLOSE partitions
DEALLOCATE partitions
 
GO

2020年5月3日日曜日

WSACleanup でやらかした話


システムで思わぬところでエラーになって、俺関係ないやん?
そういう事ありません?

今回は、Windows の 通信Socket の初期化と終了処理でやらかした話を書きます。
ソース中の一箇所、WSACleanup の呼び出し場所が間違っていました。

正しくないコード
#include <winsock2.h>

void simpleSend( const char* str ) {
  WSADATA wsaData;

  if( !WSAStartup(MAKEWORD(2,0), &wsaData) ) {
    // 送信処理
  }
  WSACleanup();  // 初期化に失敗したのにクリーンナップが呼び出される。
  return 0;
}

これだと、WSAStartup に失敗した時も WSACleanup が呼び出されてしまいます。
WSAStartup と WSACleanup の処理は、コールカウント方式で処理されます。従って、サードパーティ製のDLLで WSAStartup していたコードが巻き添えを食らって WSACleanup されてしまうため、Socket の close 処理時にエラーになってしまうという現象となりました。
WSAStartup が失敗するタイミングとしては、アプリケーション終了間際があります。アプリケーションの終了間際にサードパーティ製のDLLが例外を送出してしまうのですが、この原因が WSAStartup が失敗したにも関わらず、WSACleanup を呼び出していた事によります。

正しいコード
#include <winsock2.h>

void simpleSend( const char* str ) {
  WSADATA wsaData;

  if( !WSAStartup(MAKEWORD(2,0), &wsaData) ) {
    // 送信処理
    WSACleanup();
  }
  return 0;
}
このように、WSAStartup に失敗したら、WSACleanup を呼び出さないようにする事が重要です。

2020年2月2日日曜日

DQウォーク雑感

GIS系システムを扱ってる関係で、GIS系ゲームは体験しとかなあかんやろ?というノリで、ポケモンGOをやってたんですが、DQウォークやり始めたら、こっちの方が面白くて、最近はDQウォークをずーっとやってます。雪が降る前は、1日15kmぐらい歩いてました。
ガチャに数万とか注ぎ込む話は聞いていましたが、DQウォークをやるまでは、あまり馴染みのない世界でした。
自分だったら、装備1個に数万円とか払うぐらいなら、Marvic Proとか、Oculus Questとか、Kayak用品、キャンプ用品、カメラ・レンズ、スキー用品を買いますね。
オンライン・ゲームは運営費がかかります。なんでガチャを否定する気はありません。
ゲームが面白いので月3000円のゴールドパス分だけは運営費として課金するスタンスで楽しんでます。
職業柄、システムの実装とかバグとか、細かいところが見えてしまうんですね。
例えば、速さ対策で回復スポットの出現を遅らせるように調整したな?とか、「じゅんび」画面表示して戻ると回復スポットの読み込みが入るけど、運営側でこれに対応して読み込み遅らせるように調整しおった!とか、あ、この辺のパラメータ調整されたな?といったような事が見えてしまうんです。
コロプラの印象は、私も例に漏れず巷で書かれているように「セコイ」です。あと、運営の中に、ゲームの売上が上がるように考えるとか、ユーザに楽しんでもらえるように考えるとか、そんな事よりも、不公正許すまじ房がいる感じがしてます。
カプコンのストリートファイターでは、バグによりアッパー昇龍拳が生まれました。これを逆手にとってコンボシステムへと昇華させて今に至ると思うのですが、不公正許すまじ房にかかると、バグで一部のプレイヤーが利益を被るのはケシカラン!今すぐ、対処すべきだ!となって、バグ修正が施され、ゲームとしての魅力がどんどん削ぎ落とされていきます。
こころのドロップ率を上げたよ!→一部のガチ勢が車で周回してランクSのこころを大量に集めている!→ケシカラン!→直ちに修正すべきだ→こころのドロップ率を今から調整して大幅に下げます→ゲームつまらなくなっちゃったよ…
こんな感じの繰り返しです。
不公正許すまじ房の提言で、高確率こころドロップの出現頻度が上がったような気がしてます(勘ぐりすぎか?)。高確率を連続してゲットする行動を取るユーザをふるい落として、こころドロップ率を下げるためなのではないかと感じてます。
で、一番つまらないと感じてるのは、ガチャの確率ですね。ゴールドパスでゲットした「ふくびき」が全然当たらない。せっかく上級職とか出てきて、面白くなってきたんですが、こころも渋い感じになってきたし、ガチャに5万とか注ぎ込まないと、やりこみ要素がどんどん減ってきて、つまんないんですわ。私のようなライト課金ユーザを切り捨てて運営した方が儲かるんだったらそれもいいと思います。
メガモンスターに関しても、社会人がメガモンスター討伐を何個もこなせると思いますか?しかも、不公正許すまじ房のせいで、妙にメガモンスターの出現数も少ない感じがしてしょうがないんですわ。おまけに 「セコイ」体質なんで、もの凄いこころを集めないといけない設定にされて、端から無理ゲー気味なんですわ。集めたこころ見てもらったらわかると思うけど、たくさん集めないとダメなやつはランクも低いです。
だいたい、メガモンスターの討伐チケットを買ってもらう気あるの?メガモンスターの出現数が少なすぎでしょ?しかも地理的不公平感を消すために変に気を回して機会損失してる感ありあり。いや、もう全然理解できません。
ゲーム自体は、ほんと面白くて、DQウォークのおかげで歩きまくってるんです。なんかねー、不公正許すまじ房みたいなのがゲームをつまらなくしちゃってるんじゃないかという気がして、しょうがないんです。
ちょっとネガティブな感想ですけど、ゲームは、ほんと楽しいです。1ユーザの意見でした。